mirror of
https://github.com/R0m1k3/Loki.git
synced 2026-10-14 18:58:43 +02:00
- suppression des fonctions et types inutilises (staticcheck U1000) - sources UI, workflows, README passes en ajean - release.yml ne publie plus que les binaires ajean-* - notes de release redigees
664 lines
50 KiB
HTML
664 lines
50 KiB
HTML
<!doctype html>
|
||
<!-- GÉNÉRÉ depuis ui/src/ par tools/assemble-ui (go generate ./internal/ajean) — NE PAS ÉDITER index.html DIRECTEMENT -->
|
||
<html lang="fr"><head>
|
||
<meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1,viewport-fit=cover">
|
||
<!-- PWA standalone : "Ajouter à l'écran d'accueil" sur iPhone → app plein écran
|
||
SANS la barre d'onglets Safari (indispensable pour enregistrer des démos). -->
|
||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||
<meta name="mobile-web-app-capable" content="yes">
|
||
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
|
||
<meta name="apple-mobile-web-app-title" content="AJEAN">
|
||
<title>AJEAN</title>
|
||
<!-- Favicon (même marque « ajean » que la landing/portail) + icône PWA (écran d'accueil
|
||
iOS). SVG inline pour le favicon, PNG 180×180 en data-URI pour apple-touch-icon
|
||
(iOS n'accepte pas le SVG pour l'icône d'accueil). Aucun fichier externe requis. -->
|
||
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12'><rect width='12' height='12' rx='2' ry='2' fill='%23000'/><rect x='6' y='3' width='2' height='2' fill='%23fff'/><rect x='6' y='5' width='2' height='2' fill='%23fff'/><rect x='4' y='7' width='2' height='2' fill='%23fff'/></svg>">
|
||
<link rel="apple-touch-icon" href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAALQAAAC0CAIAAACyr5FlAAABBUlEQVR42u3SAQkAAAwCQfuX3lI4GNxF0E8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIArU2NbcYhDHOIQhzgQB+JAHIgDcSAOxIE4xCEOcYhDHIgDcSAOxIE4EAfiQBziEIc4xCEOcYgDcSAOxIE4EAfiQBziEIc4xCEOcYgDcSAOxIE4EAfiQByIQxziEIc4xIE4EAfiQByIA3EgDsQhDnGIQxziEIc4EAfiQByIAxciDsSBOBAH4kAc4hCHOMQhDnGIA3EgDsSBOBAH4kAc4hCHOMQhDnGIA3EgDsSBOBAH4kAciEMc4hCHOMQBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADfLBHMhi4TnG6EAAAAAElFTkSuQmCC">
|
||
<!-- Marque le mode PWA AVANT le rendu : la hauteur de coque et le défilement du
|
||
document en dépendent (voir styles.css). ⚠️ On NE PEUT PAS se fier au média
|
||
`display-mode:standalone` : WebKit l'adosse au `display` du MANIFESTE, or cette
|
||
app n'en a pas (elle est en `apple-mobile-web-app-capable`) — en PWA iOS il
|
||
répond donc `browser`. `navigator.standalone` est le signal juste ici ; on
|
||
garde `display-mode` en second pour les PWA Android/desktop à manifeste. -->
|
||
<script>try{var pwa=(window.navigator.standalone===true)||(window.matchMedia&&window.matchMedia('(display-mode:standalone)').matches)||(window.matchMedia&&window.matchMedia('(display-mode:fullscreen)').matches);document.documentElement.setAttribute('data-pwa',pwa?'1':'0');}catch(e){document.documentElement.setAttribute('data-pwa','0');}</script>
|
||
<!-- Applique le thème AVANT le rendu (pas de flash) : lit le choix persisté. -->
|
||
<script>try{var t=localStorage.getItem('ajean-theme')||'light';document.documentElement.setAttribute('data-theme',(t==='dark'||t.endsWith('-dark'))?'dark':'light');['hide-reasoning','hide-tools','fold-tools','hide-side','hide-stats'].forEach(function(k){document.documentElement.setAttribute('data-'+k,localStorage.getItem('ajean-'+k)==='1'?'1':'0')})}catch(e){document.documentElement.setAttribute('data-theme','light');}</script>
|
||
<style>
|
||
@@CSS@@
|
||
</style></head><body>
|
||
<button id="menubtn" onclick="toggleSide()">☰</button>
|
||
<!-- Installation d'un moteur en cours : le détail vit dans le panneau, qui est
|
||
fermé sur téléphone. Sans ce rappel, un rechargement de page donnait
|
||
l'impression que l'installation avait disparu. -->
|
||
<button id="lc-chip" onclick="lcChipOpen()" hidden></button>
|
||
<div id="backdrop" onclick="toggleSide()"></div>
|
||
<div class="side" id="side">
|
||
<div class="sidehead">
|
||
<h1 id="brand" aria-label="ajean"><span id="brand-a"><svg viewBox="0 0 4 5" shape-rendering="crispEdges" fill="currentColor"><rect x="1" y="0" width="2" height="1"/><rect x="0" y="1" width="1" height="1"/><rect x="3" y="1" width="1" height="1"/><rect x="0" y="2" width="4" height="1"/><rect x="0" y="3" width="1" height="1"/><rect x="3" y="3" width="1" height="1"/><rect x="0" y="4" width="1" height="1"/><rect x="3" y="4" width="1" height="1"/></svg></span><span id="brand-word"><svg viewBox="0 0 17 5" shape-rendering="crispEdges" fill="currentColor"><rect x="1" y="0" width="1" height="1"/><rect x="1" y="1" width="1" height="1"/><rect x="1" y="2" width="1" height="1"/><rect x="1" y="3" width="1" height="1"/><rect x="0" y="4" width="1" height="1"/><rect x="3" y="0" width="4" height="1"/><rect x="3" y="1" width="1" height="1"/><rect x="3" y="2" width="3" height="1"/><rect x="3" y="3" width="1" height="1"/><rect x="3" y="4" width="4" height="1"/><rect x="9" y="0" width="2" height="1"/><rect x="8" y="1" width="1" height="1"/><rect x="11" y="1" width="1" height="1"/><rect x="8" y="2" width="4" height="1"/><rect x="8" y="3" width="1" height="1"/><rect x="11" y="3" width="1" height="1"/><rect x="8" y="4" width="1" height="1"/><rect x="11" y="4" width="1" height="1"/><rect x="13" y="0" width="1" height="1"/><rect x="16" y="0" width="1" height="1"/><rect x="13" y="1" width="2" height="1"/><rect x="16" y="1" width="1" height="1"/><rect x="13" y="2" width="1" height="1"/><rect x="15" y="2" width="2" height="1"/><rect x="13" y="3" width="1" height="1"/><rect x="16" y="3" width="1" height="1"/><rect x="13" y="4" width="1" height="1"/><rect x="16" y="4" width="1" height="1"/></svg></span><span id="ver"></span></h1>
|
||
<span id="status-svc" class="statuspill" onclick="toggleSvcLog()" title="voir le journal du moteur">…</span>
|
||
</div>
|
||
<!-- Avertissement de lancement (App Translocation macOS) — masqué par défaut. -->
|
||
<div id="app-warn" class="muted" style="display:none;font-size:11px;line-height:1.5;margin:0 0 10px;padding:8px 10px;border-radius:8px;color:var(--warn);border:1px solid color-mix(in srgb,var(--warn) 45%,transparent);background:color-mix(in srgb,var(--warn) 12%,transparent)"></div>
|
||
<div id="model-err" style="display:none;font-size:11px;line-height:1.5;margin:0 0 10px;padding:8px 10px;border-radius:8px;color:var(--err);border:1px solid color-mix(in srgb,var(--err) 45%,transparent);background:color-mix(in srgb,var(--err) 12%,transparent)"></div>
|
||
<!-- Journal du moteur : replié par défaut, ouvert au clic sur la pastille d'état.
|
||
C'est le seul endroit où l'on voit pourquoi un modèle refuse de charger. -->
|
||
<div id="svc-log-box" style="display:none;margin:0 0 10px">
|
||
<div class="row" style="justify-content:space-between;align-items:center">
|
||
<span class="io-cap">Journal du moteur</span>
|
||
<button onclick="loadSvcLog()" title="rafraîchir">↻</button>
|
||
</div>
|
||
<pre id="svc-log" class="muted" style="font-size:10px;line-height:1.45;max-height:220px;overflow:auto;white-space:pre-wrap;word-break:break-word;margin:6px 0 0"></pre>
|
||
<!-- Emplacements : au même endroit que le journal, c'est le panneau où l'on
|
||
va quand on cherche à comprendre l'état de son installation. -->
|
||
<div class="row" style="justify-content:space-between;align-items:center;margin-top:12px">
|
||
<span class="io-cap">Emplacements</span>
|
||
</div>
|
||
<div id="paths-msg" class="muted" style="font-size:10px;line-height:1.5"></div>
|
||
</div>
|
||
<!-- GPU et RAM décrivent la même chose (les ressources de la machine) :
|
||
une seule section, deux blocs de statistique dans la même carte. -->
|
||
<details open><summary><span class="stitle">Machine</span></summary>
|
||
<div class="slist">
|
||
<div id="vram">…</div>
|
||
<div id="ram-details"><div id="ram"></div></div>
|
||
</div>
|
||
</details>
|
||
<details open id="cfg-details"><summary><span class="stitle">Config active</span></summary>
|
||
<div id="cfg" class="slist">…</div>
|
||
</details>
|
||
<details><summary><span class="stitle">Presets</span><button class="preset-add" onclick="event.preventDefault();event.stopPropagation();openPreset('')" title="ajouter">+</button></summary>
|
||
<div class="slist"><div id="presets" class="rowlist"></div></div>
|
||
</details>
|
||
<details><summary><span class="stitle">Mode agent</span><span id="agent-badge" class="sbadge"></span></summary>
|
||
<div class="slist"><label class="switchrow"><span class="switch"><input type="checkbox" id="agent-toggle" onchange="toggleAgent()"><span class="slider"></span></span> activer le mode agent<span class="help" tabindex="0" onclick="event.preventDefault();event.stopPropagation();this.classList.toggle('open')">?<span class="tip">⚠ active l'<b>accès machine</b> (shell complet via <b>bash</b> sur le serveur) — l'IA peut inspecter, lancer des scripts, écrire/lire/éditer des fichiers (timeout 30s, max 300s). Mémoire et accès internet ci-dessous ne fonctionnent que si le mode agent est activé.</span></span></label></div>
|
||
<!-- Sous-réglages du mode agent. MÊME gabarit que « Machine » / « Apparence » :
|
||
étiquette de groupe nue, puis UNE carte de lignes (.slist). Le groupe
|
||
entier est grisé/inerte quand l'agent est off (voir setAgentGate). -->
|
||
<div id="mem-block" class="gated-group">
|
||
<div class="subhead">Mémoire<span class="help" tabindex="0" onclick="event.preventDefault();event.stopPropagation();this.classList.toggle('open')">?<span id="mem-mode-desc" class="tip"></span></span></div>
|
||
<div class="slist">
|
||
<label class="switchrow"><span>mode</span>
|
||
<select id="mem-mode" onchange="setMemMode()" class="sctl">
|
||
<option value="always">auto (proactive)</option>
|
||
<option value="ondemand">sur demande</option>
|
||
<option value="off">désactivée</option>
|
||
</select>
|
||
</label>
|
||
<div id="mem-pages-bar" class="pages-bar" onclick="toggleMemPages()">
|
||
<span class="pages-title">Voir la mémoire <span id="mem-count" class="mem-count"></span></span>
|
||
<button class="addbtn" onclick="event.stopPropagation();openMem('')" title="ajouter une page mémoire">+ ajouter</button>
|
||
</div>
|
||
<div id="mem-pages-body" hidden>
|
||
<!-- Le champ de filtre garde son rembourrage de ligne ; la liste, elle,
|
||
est à fleur de carte : ses pages sont des LIGNES séparées par un
|
||
filet, pas une pile de petites cartes. -->
|
||
<div id="mem-search-row" class="rowbody" style="display:none">
|
||
<input id="mem-search" class="mem-search" type="search" placeholder="filtrer la mémoire…" oninput="renderMemList()" style="margin:0">
|
||
</div>
|
||
<div id="mem-list" class="mem-list rowlist muted" style="font-size:12px"></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div id="net-block" class="gated-group">
|
||
<div class="subhead">Accès internet<span id="internet-badge" class="sbadge"></span><span class="help" tabindex="0" onclick="event.preventDefault();event.stopPropagation();this.classList.toggle('open')">?<span class="tip">Ajoute les outils <b>web_search / web_open / web_read / web_grep</b>. Le moteur <b>intégré</b> ne demande aucune installation. Le moteur <b>Crawl4AI</b> exige un serveur que tu héberges toi-même (ex. <code>docker run -d -p 11235:11235 --shm-size=1g unclecode/crawl4ai:latest</code>) mais exécute le JavaScript des pages. Actifs seulement si le <b>mode agent</b> est actif.</span></span></div>
|
||
<div class="slist">
|
||
<label class="switchrow"><span class="switch"><input type="checkbox" id="internet-toggle" onchange="toggleInternet()"><span class="slider"></span></span> donner internet à l'IA</label>
|
||
<!-- Moteur web : « go » = intégré au binaire (rien à installer, pas de rendu
|
||
JavaScript) · « crawl4ai » = serveur externe avec Chromium headless.
|
||
Réglages propres à Crawl4AI masqués avec le moteur intégré ; l'URL et
|
||
la clé s'enregistrent en quittant le champ (onchange). -->
|
||
<div class="rowbody">
|
||
<div class="row" style="margin:0">
|
||
<select id="web-engine" class="sctl" onchange="saveWebEngine()">
|
||
<option value="go">Moteur Intégré</option>
|
||
<option value="crawl4ai">Crawl4AI</option>
|
||
</select>
|
||
</div>
|
||
<!-- Masqués AU DÉPART : le moteur par défaut est l'intégré, et ces champs
|
||
ne concernent que Crawl4AI. Affichés d'emblée puis repliés par le
|
||
chargement de l'état, ils faisaient sauter la section sous les yeux. -->
|
||
<div id="crawl-fields" style="display:none">
|
||
<div class="row">
|
||
<input id="crawl-url" type="text" placeholder="http://localhost:11235" class="sctl" onchange="saveCrawlUrl()">
|
||
</div>
|
||
<!-- Clé d'accès du serveur Crawl4AI (facultative) : envoyée en
|
||
« Authorization: Bearer … ». Jamais relue par l'API, on n'affiche
|
||
qu'un indice sur les 4 derniers caractères. -->
|
||
<div class="row">
|
||
<input id="crawl-key" type="password" autocomplete="off" placeholder="clé API (si le serveur en exige une)" class="sctl" onchange="saveCrawlKey()">
|
||
<button id="crawl-key-clear" onclick="clearCrawlKey()" style="display:none">retirer</button>
|
||
</div>
|
||
<div class="muted" style="font-size:11px;margin-top:4px">enregistré automatiquement en quittant le champ</div>
|
||
<div id="crawl-key-state" class="muted" style="font-size:11px;margin-top:3px"></div>
|
||
</div>
|
||
<div id="internet-status" class="muted" style="font-size:11px;margin-top:4px"></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div id="mcp-block" class="gated-group">
|
||
<div class="subhead">Serveurs MCP<span id="mcp-badge" class="sbadge"></span><span class="help" tabindex="0" onclick="event.preventDefault();event.stopPropagation();this.classList.toggle('open')">?<span class="tip">Connecte des <b>serveurs MCP</b> (Model Context Protocol) pour donner de nouveaux outils à l'IA : accès fichiers, bases de données, APIs métier… <b>stdio</b> = programme lancé localement (npx/uvx/binaire) · <b>http</b> = serveur distant. ⚠ un serveur stdio exécute du code sur cette machine, au même titre que le shell.</span></span></div>
|
||
<div class="slist">
|
||
<div class="rowbody">
|
||
<!-- Même texte que celui rendu après chargement quand il n'y a aucun
|
||
serveur : sinon la carte partait vide puis grandissait d'une ligne. -->
|
||
<div id="mcp-list" class="mcp-list"><div class="muted" style="font-size:12px">(aucun serveur — ajoute un serveur fichiers, une base de données, une API…)</div></div>
|
||
<button class="addbtn" style="margin-top:6px" onclick="openMcp('')">+ ajouter un serveur</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div id="param-block" class="gated-group">
|
||
<div class="subhead">Paramètres</div>
|
||
<div class="slist">
|
||
<label class="switchrow"><span class="switch"><input type="checkbox" id="compact-toggle" onchange="toggleCompact()"><span class="slider"></span></span> compactage automatique du contexte<span class="help" tabindex="0" onclick="event.preventDefault();event.stopPropagation();this.classList.toggle('open')">?<span class="tip">activé par défaut : quand la conversation approche de la limite de contexte, les vieux tours sont résumés automatiquement (façon Hermes) au lieu de t'imposer un « vider ». Discussions quasi illimitées sans perdre le fil.</span></span></label>
|
||
</div>
|
||
</div>
|
||
</details>
|
||
<details><summary><span class="stitle">Apparence</span></summary>
|
||
<div class="slist">
|
||
<label class="switchrow rev"><span>mode sombre</span>
|
||
<span class="switch"><input type="checkbox" id="theme-dark" onchange="toggleThemeDark()"><span class="slider"></span></span>
|
||
</label>
|
||
</div>
|
||
<div class="subhead">Affichage</div>
|
||
<div class="slist">
|
||
<label class="switchrow"><span class="switch"><input type="checkbox" id="view-hide-reasoning" onchange="toggleView('hide-reasoning')"><span class="slider"></span></span> masquer le raisonnement</label>
|
||
<label class="switchrow"><span class="switch"><input type="checkbox" id="view-hide-tools" onchange="toggleView('hide-tools')"><span class="slider"></span></span> masquer les appels d'outils</label>
|
||
<label class="switchrow"><span class="switch"><input type="checkbox" id="view-fold-tools" onchange="toggleView('fold-tools')"><span class="slider"></span></span> garder les bulles repliées<span class="help" tabindex="0" onclick="event.preventDefault();event.stopPropagation();this.classList.toggle('open')">?<span class="tip">Les bulles de raisonnement et d'outil arrivent <b>déjà fermées</b> au lieu de s'ouvrir puis se refermer. Un clic sur leur étiquette les déplie.</span></span></label>
|
||
<label class="switchrow"><span class="switch"><input type="checkbox" id="view-hide-side" onchange="toggleView('hide-side')"><span class="slider"></span></span> barre latérale escamotable<span class="help" tabindex="0" onclick="event.preventDefault();event.stopPropagation();this.classList.toggle('open')">?<span class="tip">La barre latérale devient un tiroir <b>même sur ordinateur</b> : elle reste fermée et s'ouvre avec le bouton ☰. Toute la largeur pour la conversation.</span></span></label>
|
||
<label class="switchrow"><span class="switch"><input type="checkbox" id="view-hide-stats" onchange="toggleView('hide-stats')"><span class="slider"></span></span> masquer la vitesse de génération<span class="help" tabindex="0" onclick="event.preventDefault();event.stopPropagation();this.classList.toggle('open')">?<span class="tip">Sous chaque réponse : <b>prefill</b> (lecture du contexte) et <b>decode</b> (écriture), en tokens et en tokens/seconde. Coche pour ne plus les afficher.</span></span></label>
|
||
</div>
|
||
</details>
|
||
<details><summary><span class="stitle">System prompt</span></summary>
|
||
<div class="slist"><div class="rowbody">
|
||
<textarea id="sysprompt" placeholder="(optionnel) ex: tu es un assistant concis en français" oninput="saveSys()" class="sctl"></textarea>
|
||
</div></div>
|
||
</details>
|
||
<details><summary><span class="stitle">Accès OpenAI</span></summary>
|
||
<div class="subhead">Endpoint compatible OpenAI<span class="help" tabindex="0" onclick="event.preventDefault();event.stopPropagation();this.classList.toggle('open')">?<span class="tip">Branchez n'importe quel client compatible OpenAI (OpenCode, Continue, Cursor…) sur ces adresses. Si une clé est définie, le client doit envoyer <code>Authorization: Bearer <clé></code>.</span></span></div>
|
||
<!-- UNE carte pour tout l'endpoint : adresse locale, bascule « public » et
|
||
adresse publique sont un seul sujet — trois cartes accolées donnaient
|
||
trois îlots pour un même réglage. -->
|
||
<div class="slist">
|
||
<div class="rowbody">
|
||
<div class="muted" style="margin:0 0 6px">Adresse locale (réseau) — modèle <code>ajean</code></div>
|
||
<div class="row" style="margin:0">
|
||
<input id="oai-url" readonly class="io-input">
|
||
<button class="iconbtn" onclick="copyText(document.getElementById('oai-url').value,'adresse copiée')" title="copier"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2.5"/><path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1"/></svg></button>
|
||
</div>
|
||
</div>
|
||
<label class="switchrow"><span class="switch"><input type="checkbox" id="oai-public-toggle" onchange="toggleOAIPublic()"><span class="slider"></span></span> exposer en public (ajean.link)<span class="help" tabindex="0" onclick="event.preventDefault();event.stopPropagation();this.classList.toggle('open')">?<span class="tip">Rend votre IA joignable depuis <b>n'importe où</b> via <code>https://<machine>.oai.ajean.link/v1</code> (pour brancher un SaaS). Le trafic est chiffré <b>de bout en bout</b> jusqu'à ce serveur — le relais ajean.link ne voit rien. Nécessite l'accès distant actif.</span></span></label>
|
||
<div id="oai-public-wrap" class="rowbody" style="display:none">
|
||
<div class="muted" style="margin:0 0 6px">Adresse publique — chiffrée de bout en bout, accessible partout</div>
|
||
<div class="row" style="margin:0">
|
||
<input id="oai-public-url" readonly class="io-input">
|
||
<button class="iconbtn" onclick="copyText(document.getElementById('oai-public-url').value,'adresse publique copiée')" title="copier"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2.5"/><path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1"/></svg></button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="subhead">Clé API<span class="help" tabindex="0" onclick="event.preventDefault();event.stopPropagation();this.classList.toggle('open')">?<span class="tip">Protège l'endpoint des complétions (llama-server). Indispensable avant d'exposer le serveur sur internet. Modifier la clé <b>redémarre</b> le service.</span></span></div>
|
||
<div class="slist"><div class="rowbody">
|
||
<div class="row" style="margin:0">
|
||
<input id="oai-key" readonly class="io-input">
|
||
<button id="oai-key-eye" onclick="toggleKeyReveal()" title="afficher / masquer">afficher</button>
|
||
<button class="iconbtn" onclick="copyApiKey()" title="copier la clé"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2.5"/><path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1"/></svg></button>
|
||
</div>
|
||
<div class="row" style="margin-bottom:0">
|
||
<button onclick="apiKeyAction('generate')">générer</button>
|
||
<button onclick="apiKeySet()">définir…</button>
|
||
<button onclick="apiKeyAction('clear')">retirer</button>
|
||
</div>
|
||
</div></div>
|
||
</details>
|
||
<details id="remote-details"><summary><span class="stitle">Accès distant</span><span id="remote-badge" class="sbadge"></span></summary>
|
||
<div class="subhead">ajean.link<span class="help" tabindex="0" onclick="event.preventDefault();event.stopPropagation();this.classList.toggle('open')">?<span class="tip">Accédez à ce serveur AJEAN depuis <b>n'importe où</b>, sans ouvrir de port, à travers le relais <b>ajean.link</b>. Le trafic est chiffré de <b>bout en bout</b> : le relais ne voit rien. Abonnement 4,80 €/mois.</span></span></div>
|
||
<!-- Non connecté : un seul bouton, tout se passe dans la popup (compte + paiement). -->
|
||
<div id="remote-off" class="slist"><div class="rowbody">
|
||
<p class="muted" style="margin:0 0 8px">Ouvrez l'accès distant à ce serveur en quelques clics, sans terminal.</p>
|
||
<button onclick="remoteConnect()" style="border-color:var(--accent);color:var(--accent)">Connecter à ajean.link</button>
|
||
</div></div>
|
||
<!-- Connecté : URL publique + appairage + déconnexion. Le bouton « démarrer
|
||
le tunnel » vit DANS la carte de l'adresse : c'est l'action de cette
|
||
ligne, il flottait auparavant entre deux cartes. -->
|
||
<div id="remote-on" style="display:none">
|
||
<div class="slist"><div class="rowbody">
|
||
<div class="muted" style="margin:0 0 6px">Votre adresse d'accès distant</div>
|
||
<div class="row" style="margin:0">
|
||
<input id="remote-url" readonly class="io-input">
|
||
<button class="iconbtn" onclick="copyText(document.getElementById('remote-url').value,'adresse copiée')" title="copier"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2.5"/><path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1"/></svg></button>
|
||
<button class="iconbtn" onclick="window.open(document.getElementById('remote-url').value,'_blank')" title="ouvrir"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M18 13v6a2 2 0 01-2 2H5a2 2 0 01-2-2V8a2 2 0 012-2h6"/><path d="M15 3h6v6"/><path d="M10 14L21 3"/></svg></button>
|
||
</div>
|
||
<div id="remote-status" class="muted" style="margin-top:6px"></div>
|
||
<div class="row" style="margin-bottom:0"><button id="remote-start" onclick="remoteStart()" style="display:none;border-color:var(--accent);color:var(--accent)">démarrer le tunnel</button></div>
|
||
</div></div>
|
||
<div class="subhead">Première connexion<span class="help" tabindex="0" onclick="event.preventDefault();event.stopPropagation();this.classList.toggle('open')">?<span class="tip">À votre première ouverture du portail distant, confirmez l'<b>empreinte</b> et saisissez le <b>code d'appairage</b> : cela verrouille la boîte noire sur CE serveur (anti-interception). Une seule fois par appareil.</span></span></div>
|
||
<div class="slist"><div class="rowbody">
|
||
<div class="row" style="margin:0"><button onclick="remotePairCode()">Afficher l'empreinte et le code d'appairage</button></div>
|
||
<div id="remote-pair" class="muted" style="display:none;margin-top:6px"></div>
|
||
<div class="row" style="margin-bottom:0"><button onclick="remoteDisconnect()" class="btn-danger-o">déconnecter l'accès distant</button></div>
|
||
</div></div>
|
||
</div>
|
||
</details>
|
||
<details id="lc-details"><summary><span class="stitle">Moteur</span></summary>
|
||
<!-- Une seule carte : état, choix du mode et journal d'installation sont le
|
||
même sujet. Les trois modes sont des LIGNES de cette carte (elles
|
||
étaient des cartes dans une carte). -->
|
||
<div class="slist">
|
||
<!-- La pastille « conseillé » n'est PAS écrite ici : le bon choix dépend de
|
||
la machine (voir lcRenderReco). Sur Linux + NVIDIA le précompilé ne
|
||
donne que du Vulkan — llama.cpp ne publie aucun binaire CUDA Linux —
|
||
alors que compiler donne du CUDA natif. -->
|
||
<div class="lc-modes">
|
||
<div class="lc-mode" id="lc-mode-fast" onclick="lcPick('fast')">
|
||
<div class="lc-mode-t">llama.cpp précompilé <span class="lc-reco" id="lc-reco-fast" hidden>conseillé</span></div>
|
||
<div class="lc-mode-d" id="lc-desc-fast">binaire officiel téléchargé tel quel, prêt en ~2 min</div>
|
||
<div class="lc-mode-state" id="lc-fast-state"></div>
|
||
</div>
|
||
<div class="lc-mode" id="lc-mode-opt" onclick="lcPick('opt')">
|
||
<div class="lc-mode-t">llama.cpp compilé <span class="lc-reco" id="lc-reco-opt" hidden>conseillé</span></div>
|
||
<div class="lc-mode-d" id="lc-desc-opt">compilé ici pour votre machine — plus rapide, plus long à installer</div>
|
||
<div class="lc-mode-state" id="lc-opt-state"></div>
|
||
</div>
|
||
<div class="lc-mode" id="lc-mode-custom" onclick="openCustomBackends()">
|
||
<div class="lc-mode-t">llama.cpp personnalisé</div>
|
||
<div class="lc-mode-d">un fork de llama.cpp pour modèles spéciaux (quant ternaire…)</div>
|
||
<div class="lc-mode-state" id="lc-custom-state"></div>
|
||
</div>
|
||
</div>
|
||
<div id="lc-job" class="rowbody" style="display:none">
|
||
<div id="lc-job-phase" style="font-size:12px;display:flex;align-items:center;gap:6px"></div>
|
||
<div class="lc-logbar" onclick="lcToggleLog()"><span id="lc-log-caret">▸</span> détails</div>
|
||
<pre id="lc-log" hidden></pre>
|
||
</div>
|
||
</div>
|
||
<!-- Ces trois boutons pilotent le service qui fait tourner llama-server :
|
||
leur place est ici, avec le moteur, et pas dans une section « Service »
|
||
à part qui ne contenait qu'eux. -->
|
||
<div class="subhead">Service du moteur<span class="help" tabindex="0" onclick="event.preventDefault();event.stopPropagation();this.classList.toggle('open')">?<span class="tip">Démarre, redémarre ou arrête <b>llama-server</b> — le processus qui charge le modèle et répond. Un redémarrage recharge le modèle du preset actif (quelques secondes à quelques minutes selon sa taille).</span></span></div>
|
||
<div class="slist"><div class="rowbody">
|
||
<div class="row" style="margin:0">
|
||
<button onclick="act('start')">démarrer</button>
|
||
<button onclick="act('restart')">redémarrer</button>
|
||
<button onclick="act('stop')">arrêter</button>
|
||
</div>
|
||
</div></div>
|
||
</details>
|
||
<details id="upd-details"><summary><span class="stitle">Actions</span></summary>
|
||
<div class="slist"><div class="rowbody">
|
||
<div class="row" style="margin:0">
|
||
<button id="upd-check" onclick="checkUpdate()">↥ Vérifier les mises à jour</button>
|
||
</div>
|
||
<div id="upd-msg" class="muted"></div>
|
||
<div class="row" style="margin-bottom:0">
|
||
<button onclick="loadAll()">↻ refresh</button>
|
||
<button onclick="resetChat()">⌫ clear chat</button>
|
||
<button id="btn-bench" onclick="runBenchUI()">bench</button>
|
||
</div>
|
||
</div></div>
|
||
</details>
|
||
</div>
|
||
<div class="main" style="position:relative">
|
||
<div id="chat"></div>
|
||
<!-- Voile de chargement du fil : sans lui, une connexion lente donne un chat
|
||
VIDE indiscernable d'une conversation vide. Masqué au signal {caught_up}. -->
|
||
<div id="chat-loading"><span class="cl-dots"><span></span><span></span><span></span></span><span id="chat-loading-text">chargement de la conversation…</span></div>
|
||
<!-- Accueil : affiché UNIQUEMENT quand le fil est réellement vide (et une fois
|
||
le replay terminé, sinon il clignoterait au chargement de chaque page).
|
||
Le logo est cloné depuis #brand au démarrage — une seule source de vérité
|
||
pour le dessin des lettres. -->
|
||
<div id="chat-empty" aria-hidden="true">
|
||
<div id="ce-logo"></div>
|
||
<!-- Une seule ligne, NEUTRE en genre : l'UI est utilisée par tout le monde,
|
||
pas de civilité ni d'accord qui obligerait à deviner. -->
|
||
<div id="ce-title">On commence par quoi ?</div>
|
||
</div>
|
||
<button id="scrollbtn" onclick="jumpBottom()" title="aller en bas">↓</button>
|
||
<div id="composer">
|
||
<div id="ctxmeta">
|
||
<div id="ctxtrack"><div id="ctx-fill"></div></div>
|
||
<span id="status-preset" class="muted" style="font-size:10px;letter-spacing:.03em;white-space:nowrap;overflow:hidden;text-overflow:ellipsis"></span>
|
||
<span style="display:flex;align-items:center;gap:10px;margin-left:auto">
|
||
<span id="ctx-text" class="muted">contexte 0 / 0</span>
|
||
<button id="ctx-compact" onclick="compactContext()">compacter</button>
|
||
</span>
|
||
</div>
|
||
<div id="inputbar">
|
||
<textarea id="input" rows="1" placeholder="message…" onkeydown="onKey(event)" oninput="autoGrow(this)"></textarea>
|
||
<button id="send" onclick="send()">send</button>
|
||
<button id="stop" onclick="stopGen()" style="display:none;background:var(--danger-bg);border-color:var(--err);color:var(--err)">stop</button>
|
||
</div>
|
||
<div id="sendhint" class="muted">Entrée pour envoyer · Maj+Entrée = nouvelle ligne</div>
|
||
</div>
|
||
</div>
|
||
<div id="toast"></div>
|
||
<div id="bench-modal" style="display:none;position:fixed;inset:0;background:rgba(0,0,0,.45);z-index:30;align-items:center;justify-content:center;padding:20px" onclick="if(event.target===this)closeBenchModal()">
|
||
<div style="background:var(--bg);border:1px solid var(--border);border-radius:8px;width:100%;max-width:480px;display:flex;flex-direction:column">
|
||
<div style="padding:14px 16px;border-bottom:1px solid var(--border);display:flex;justify-content:space-between;align-items:center">
|
||
<strong>Benchmark</strong>
|
||
<button onclick="closeBenchModal()" style="margin:0">×</button>
|
||
</div>
|
||
<div id="bench-body" style="padding:18px 20px;display:flex;flex-direction:column;gap:12px"></div>
|
||
<div style="padding:12px 16px;border-top:1px solid var(--border);display:flex;justify-content:flex-end;gap:6px">
|
||
<button onclick="closeBenchModal()">fermer</button>
|
||
<button id="bench-rerun" onclick="runBenchUI()" style="border-color:var(--accent);color:var(--accent)">relancer</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div id="ask-modal" class="ask-overlay" style="display:none" onclick="if(event.target===this)askResolve(false)">
|
||
<div class="ask-box">
|
||
<div id="ask-title" class="ask-title"></div>
|
||
<div id="ask-msg" class="ask-msg"></div>
|
||
<input id="ask-input" class="ask-input" style="display:none">
|
||
<!-- Option facultative de la confirmation (ex. « supprimer aussi le .gguf ») :
|
||
sa place est ICI, dans la boîte qui pose la question, plutôt que dans le
|
||
pied de l'éditeur où elle occupait de la place en permanence. -->
|
||
<label id="ask-check" class="pe-delmodel" style="display:none"><input type="checkbox" id="ask-check-input"><span id="ask-check-label"></span></label>
|
||
<div class="ask-actions">
|
||
<button id="ask-cancel" onclick="askResolve(false)">Annuler</button>
|
||
<button id="ask-ok" class="ask-ok" onclick="askResolve(true)">Confirmer</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div id="lc-custom-modal" class="modal-ov" style="display:none" onclick="if(event.target===this)closeCustomBackends()">
|
||
<div class="modal-card" style="max-width:560px">
|
||
<div class="modal-head">
|
||
<strong>Backends personnalisés</strong>
|
||
<button class="modal-x" onclick="closeCustomBackends()" aria-label="Fermer">×</button>
|
||
</div>
|
||
<div class="modal-body mform">
|
||
<div class="muted" style="font-size:12px;line-height:1.5">Compile un fork de llama.cpp depuis un dépôt Git — pour les modèles qui exigent un moteur spécial (quant ternaire, etc.). Un backend personnalisé <b>ne remplace pas</b> le moteur : vous le choisissez ensuite <b>par modèle</b> (bouton ✎ → section Moteur → « backend détecté »).</div>
|
||
<div class="fld">
|
||
<label class="fld-l">Installés</label>
|
||
<div id="lc-custom-list" class="mcp-list"></div>
|
||
</div>
|
||
<div class="fld">
|
||
<label class="fld-l" for="lc-custom-url">Installer depuis un dépôt Git</label>
|
||
<input id="lc-custom-url" class="ctl" placeholder="https://github.com/owner/llama.cpp-fork">
|
||
</div>
|
||
<div class="fld">
|
||
<label class="fld-l" for="lc-custom-name">Nom <span class="opt">(optionnel — déduit de l'URL sinon)</span></label>
|
||
<input id="lc-custom-name" class="ctl" placeholder="llama.cpp-prism">
|
||
</div>
|
||
</div>
|
||
<div class="modal-foot">
|
||
<div class="foot-l"></div>
|
||
<div class="foot-r">
|
||
<button onclick="closeCustomBackends()">fermer</button>
|
||
<button class="btn-save" onclick="lcInstallCustom()">Installer & compiler</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div id="mcp-modal" class="modal-ov" style="display:none" onclick="if(event.target===this)closeMcp()">
|
||
<div class="modal-card" style="max-width:560px">
|
||
<div class="modal-head">
|
||
<strong id="mcp-modal-title">Serveur MCP</strong>
|
||
<button class="modal-x" onclick="closeMcp()" aria-label="Fermer">×</button>
|
||
</div>
|
||
<div class="modal-body mform">
|
||
<div class="fld">
|
||
<label class="fld-l" for="mcp-name">Nom <span class="opt">(identifiant du serveur, ex: mon-serveur-fichiers)</span></label>
|
||
<input id="mcp-name" class="ctl" placeholder="mon-serveur">
|
||
</div>
|
||
<div class="fld">
|
||
<label class="fld-l">Transport</label>
|
||
<div class="seg">
|
||
<label><input type="radio" name="mcp-transport" value="stdio" onchange="mcpTransportUI('stdio')"><span>stdio (local)</span></label>
|
||
<label><input type="radio" name="mcp-transport" value="http" onchange="mcpTransportUI('http')"><span>http (distant)</span></label>
|
||
</div>
|
||
</div>
|
||
<div id="mcp-stdio-fields">
|
||
<div class="fld">
|
||
<label class="fld-l" for="mcp-command">Commande <span class="opt">(exécutable, ex: npx, uvx, python)</span></label>
|
||
<input id="mcp-command" class="ctl" placeholder="npx">
|
||
</div>
|
||
<div class="fld">
|
||
<label class="fld-l" for="mcp-args">Arguments <span class="opt">(un par ligne)</span></label>
|
||
<textarea id="mcp-args" class="ctl ta" spellcheck="false" style="min-height:70px" placeholder="-y @scope/mon-serveur-mcp"></textarea>
|
||
</div>
|
||
<div class="fld">
|
||
<label class="fld-l" for="mcp-env">Variables d'environnement <span class="opt">(KEY=valeur, une par ligne — optionnel)</span></label>
|
||
<textarea id="mcp-env" class="ctl ta" spellcheck="false" style="min-height:44px" placeholder="API_TOKEN=xxxx"></textarea>
|
||
</div>
|
||
</div>
|
||
<div id="mcp-http-fields" style="display:none">
|
||
<div class="fld">
|
||
<label class="fld-l" for="mcp-url">URL <span class="opt">(endpoint Streamable HTTP)</span></label>
|
||
<input id="mcp-url" class="ctl" placeholder="https://exemple.com/mcp">
|
||
</div>
|
||
<div class="fld">
|
||
<label class="fld-l" for="mcp-headers">En-têtes <span class="opt">(Nom: valeur, un par ligne — ex: Authorization: Bearer …)</span></label>
|
||
<textarea id="mcp-headers" class="ctl ta" spellcheck="false" style="min-height:44px" placeholder="Authorization: Bearer sk-…"></textarea>
|
||
</div>
|
||
</div>
|
||
<div id="mcp-modal-status" class="muted" style="font-size:12px;min-height:16px"></div>
|
||
<div id="mcp-tools-section" style="display:none">
|
||
<div class="subhead" style="margin-top:6px">Outils <span id="mcp-tools-count" class="muted" style="text-transform:none;letter-spacing:0;font-weight:400;font-family:var(--font)"></span><span class="help" tabindex="0" onclick="event.preventDefault();event.stopPropagation();this.classList.toggle('open')">?<span class="tip">Décoche un outil pour le masquer à l'IA sans déconnecter le serveur. Utile pour ne garder que ce dont tu as besoin.</span></span></div>
|
||
<div id="mcp-tools-list" class="mcp-tools-list"></div>
|
||
</div>
|
||
</div>
|
||
<div class="modal-foot">
|
||
<div class="foot-l">
|
||
<button id="mcp-del" class="btn-danger" onclick="deleteMcp()" style="display:none">supprimer</button>
|
||
</div>
|
||
<div class="foot-r">
|
||
<button onclick="closeMcp()">annuler</button>
|
||
<button class="btn-save" onclick="saveMcp()">enregistrer</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div id="modal" class="modal-ov" style="display:none">
|
||
<div class="modal-card">
|
||
<div class="modal-head">
|
||
<strong id="modal-title">Preset</strong>
|
||
<button class="modal-x" onclick="closeModal()" aria-label="Fermer">×</button>
|
||
</div>
|
||
<!-- Chargement : la modale s'ouvre tout de suite, mais son contenu n'arrive
|
||
qu'après (jusqu'à 3 requêtes en accès distant). Ce voile se pose PAR-DESSUS
|
||
le formulaire, qui reste en place (invisible) : la carte garde sa taille
|
||
finale, donc pas de saut au moment où le contenu apparaît. -->
|
||
<div class="modal-load"><span class="spinner"></span></div>
|
||
<div class="pe-body">
|
||
<div class="pe-group">
|
||
<div class="pe-gh">Nom</div>
|
||
<div class="pe-list">
|
||
<div class="pe-row stack plain"><input id="m-name" class="pe-inp" placeholder="Nom du preset"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="m-model-row" style="display:none;flex-direction:column;gap:26px">
|
||
<div class="pe-group">
|
||
<div class="pe-gh">Modèle</div>
|
||
<div class="pe-list">
|
||
<div class="pe-row stack">
|
||
<span class="pe-row-l">Fichier</span>
|
||
<span class="pe-selc wide"><select id="m-model" onchange="onPickModel()"></select></span>
|
||
</div>
|
||
<div class="pe-row">
|
||
<span class="pe-row-l">Quantization<span class="pe-sub">auto si vide</span></span>
|
||
<span class="pe-row-c"><input id="m-quant" class="pe-val" oninput="applyQuant()" placeholder="Auto"></span>
|
||
</div>
|
||
<div class="pe-row stack">
|
||
<span class="pe-row-l">Télécharger un modèle <span class="pe-sub">lien direct vers un .gguf</span></span>
|
||
<div class="pe-inpbtn">
|
||
<input id="m-hf-url" class="pe-inp" placeholder="https://…/model.gguf">
|
||
<button id="m-hf-btn" onclick="startDownload()" title="Télécharger">↓</button>
|
||
</div>
|
||
<div id="m-hf-bar" class="pe-bar" style="display:none"><i></i></div>
|
||
<div id="m-hf-progress" class="pe-note" style="display:none"></div>
|
||
<button id="m-hf-cancel" class="pe-link" onclick="cancelDownload()" style="display:none">Annuler le téléchargement</button>
|
||
</div>
|
||
<div class="pe-row">
|
||
<span class="pe-row-l">Destination</span>
|
||
<span class="pe-selc"><select id="m-hf-dir"></select></span>
|
||
</div>
|
||
<div id="m-dirs-toggle" class="pe-row" style="cursor:pointer" onclick="toggleModelDirs()">
|
||
<span class="pe-row-l">Dossiers de modèles<span class="pe-sub">chercher les .gguf ailleurs (disque externe…)</span></span>
|
||
<span class="pe-row-c"><span id="m-dirs-caret" class="pe-caret"></span></span>
|
||
</div>
|
||
<div id="m-dirs-body" class="pe-row stack" style="display:none">
|
||
<div id="m-dirs-list" class="pe-note"></div>
|
||
<div class="pe-inpbtn">
|
||
<input id="m-dir-path" class="pe-inp" placeholder="/media/disque-externe/models">
|
||
<button id="m-dir-add" onclick="addModelDir()" title="Ajouter ce dossier">+</button>
|
||
</div>
|
||
<div class="pe-note">Ces dossiers servent aussi de destination de téléchargement.</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="pe-group">
|
||
<div class="pe-gh">Moteur</div>
|
||
<div class="pe-seg">
|
||
<label><input type="radio" name="m-be" value="fast" onchange="onBackendMode('fast')"><span>Précompilé</span><span class="be-note" id="be-fast-note"></span></label>
|
||
<label><input type="radio" name="m-be" value="opt" onchange="onBackendMode('opt')"><span>Compilé</span><span class="be-note" id="be-opt-note"></span></label>
|
||
<label><input type="radio" name="m-be" value="custom" onchange="onBackendMode('custom')"><span>Personnalisé</span></label>
|
||
</div>
|
||
<div id="m-backend-custom" class="pe-list" style="display:none;margin-top:8px">
|
||
<div class="pe-row">
|
||
<span class="pe-row-l">Backend</span>
|
||
<span class="pe-row-c"><span class="pe-selc"><select id="m-backend-detected" onchange="onPickDetected()"></select></span></span>
|
||
</div>
|
||
<div class="pe-row stack">
|
||
<input id="m-backend-path" class="pe-inp" oninput="onCustomPath()" placeholder="ou un chemin vers llama-server(.exe)">
|
||
<div class="pe-note" id="be-drop-hint"></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Cartes graphiques : la liste vient du moteur choisi juste au-dessus,
|
||
car les noms ET l'ordre dépendent du backend compilé dedans. Le
|
||
groupe reste masqué tant qu'on n'a pas AU MOINS DEUX cartes : avec
|
||
une seule, il n'y a rien à arbitrer. -->
|
||
<div class="pe-group" id="m-gpu-group" hidden>
|
||
<div class="pe-gh">Cartes graphiques</div>
|
||
<div class="pe-list" id="m-gpu-list"></div>
|
||
<div class="pe-note" id="m-gpu-note"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Une seule liste : les anciens « réglages avancés » repliés sont
|
||
désormais à la suite. Les valeurs d'abord, puis TOUS les interrupteurs
|
||
groupés en fin de liste — chercher un toggle perdu entre deux champs
|
||
numériques n'aidait personne. -->
|
||
<div id="m-settings-row" class="pe-group" style="display:none">
|
||
<div class="pe-gh">Réglages</div>
|
||
<div class="pe-list">
|
||
<div class="pe-row">
|
||
<span class="pe-row-l">Contexte</span>
|
||
<span class="pe-row-c"><input id="s-ctx" class="pe-val" type="number" min="0" step="1024" placeholder="32768" oninput="cfgWriteKey('CTX', this.value)"><span class="pe-unit">tok</span></span>
|
||
</div>
|
||
<div class="pe-row">
|
||
<span class="pe-row-l">Couches sur GPU<span class="pe-sub">999 = tout sur le GPU</span></span>
|
||
<span class="pe-row-c"><input id="s-ngl" class="pe-val" type="number" min="0" step="1" placeholder="999" oninput="cfgWriteKey('NGL', this.value)"></span>
|
||
</div>
|
||
<div class="pe-row">
|
||
<span class="pe-row-l">Experts MoE sur CPU<span class="pe-sub">vide = non</span></span>
|
||
<span class="pe-row-c"><input id="s-moe" class="pe-val" type="number" min="0" step="1" placeholder="—" oninput="eaSetValued('--n-cpu-moe', this.value)"></span>
|
||
</div>
|
||
<div class="pe-row">
|
||
<span class="pe-row-l">Threads CPU<span class="pe-sub">0 = auto</span></span>
|
||
<span class="pe-row-c"><input id="s-threads" class="pe-val" type="number" min="0" step="1" placeholder="0" oninput="cfgWriteKey('THREADS', this.value)"></span>
|
||
</div>
|
||
<div class="pe-row">
|
||
<span class="pe-row-l">Threads (batch)<span class="pe-sub">0 = auto</span></span>
|
||
<span class="pe-row-c"><input id="s-tbatch" class="pe-val" type="number" min="0" step="1" placeholder="0" oninput="cfgWriteKey('THREADS_BATCH', this.value)"></span>
|
||
</div>
|
||
<div class="pe-row">
|
||
<span class="pe-row-l">BATCH</span>
|
||
<span class="pe-row-c"><input id="s-batch" class="pe-val" type="number" min="0" step="256" placeholder="2048" oninput="cfgWriteKey('BATCH', this.value)"></span>
|
||
</div>
|
||
<div class="pe-row">
|
||
<span class="pe-row-l">UBATCH</span>
|
||
<span class="pe-row-c"><input id="s-ubatch" class="pe-val" type="number" min="0" step="128" placeholder="512" oninput="cfgWriteKey('UBATCH', this.value)"></span>
|
||
</div>
|
||
<div class="pe-row">
|
||
<span class="pe-row-l">Cache KV<span class="pe-sub">compression du contexte</span></span>
|
||
<span class="pe-row-c"><span class="pe-selc"><select id="s-kv" onchange="cfgWriteKey('KV_TYPE', this.value)">
|
||
<option value="">f16 (max qualité)</option>
|
||
<option value="q8_0">q8_0 (léger)</option>
|
||
<option value="q4_0">q4_0 (très léger)</option>
|
||
<option value="q5_1">q5_1</option>
|
||
<option value="bf16">bf16</option>
|
||
</select></span></span>
|
||
</div>
|
||
<!-- Décodage spéculatif : le type vient de la liste que llama-server
|
||
accepte pour --spec-type. Le nombre de jetons anticipés
|
||
(--spec-draft-n-max) n'a de sens qu'une fois un type choisi, donc
|
||
sa ligne n'apparaît qu'à ce moment. -->
|
||
<div class="pe-row">
|
||
<span class="pe-row-l">Décodage spéculatif<span class="pe-sub">anticipe des jetons pour accélérer</span></span>
|
||
<span class="pe-row-c"><span class="pe-selc"><select id="s-spec" onchange="onSpecType()">
|
||
<option value="">non</option>
|
||
<optgroup label="Modèle brouillon">
|
||
<option value="draft-mtp">MTP (intégré au modèle)</option>
|
||
<option value="draft-eagle3">EAGLE-3</option>
|
||
<option value="draft-simple">modèle brouillon séparé</option>
|
||
<option value="draft-dflash">dFlash</option>
|
||
<option value="draft-dspark">dSpark</option>
|
||
</optgroup>
|
||
<optgroup label="N-grammes (sans modèle brouillon)">
|
||
<option value="ngram-simple">n-grammes</option>
|
||
<option value="ngram-mod">n-grammes (mod)</option>
|
||
<option value="ngram-cache">n-grammes (cache)</option>
|
||
<option value="ngram-map-k">n-grammes (map-k)</option>
|
||
<option value="ngram-map-k4v">n-grammes (map-k4v)</option>
|
||
</optgroup>
|
||
</select></span></span>
|
||
</div>
|
||
<div class="pe-row" id="s-spec-n-row" style="display:none">
|
||
<span class="pe-row-l">Jetons anticipés<span class="pe-sub">par étape — défaut 3</span></span>
|
||
<span class="pe-row-c"><input id="s-spec-n" class="pe-val" type="number" min="1" step="1" placeholder="3" oninput="eaSetValued('--spec-draft-n-max', this.value)"></span>
|
||
</div>
|
||
<div class="pe-row">
|
||
<span class="pe-row-l">Raisonnement<span class="pe-sub">réflexion étape par étape</span></span>
|
||
<span class="pe-row-c"><label class="pe-switch"><input type="checkbox" id="s-reasoning" onchange="cfgWriteKey('REASONING', this.checked?'on':'')"><span class="pe-sl"></span></label></span>
|
||
</div>
|
||
<div class="pe-row">
|
||
<span class="pe-row-l">Flash attention<span class="pe-sub">plus rapide, moins de VRAM</span></span>
|
||
<span class="pe-row-c"><label class="pe-switch"><input type="checkbox" id="s-flash" onchange="eaSetValued('--flash-attn', this.checked?'on':'')"><span class="pe-sl"></span></label></span>
|
||
</div>
|
||
<div class="pe-row">
|
||
<span class="pe-row-l">Garder en RAM<span class="pe-sub">évite le swap (mlock)</span></span>
|
||
<span class="pe-row-c"><label class="pe-switch"><input type="checkbox" id="s-mlock" onchange="eaToggleFlag('--mlock', this.checked)"><span class="pe-sl"></span></label></span>
|
||
</div>
|
||
<div class="pe-row">
|
||
<span class="pe-row-l">Charger tout en mémoire<span class="pe-sub">désactive le mmap</span></span>
|
||
<span class="pe-row-c"><label class="pe-switch"><input type="checkbox" id="s-nommap" onchange="eaToggleFlag('--no-mmap', this.checked)"><span class="pe-sl"></span></label></span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="m-raw" class="pe-group">
|
||
<div class="pe-gh" id="m-raw-head">Configuration</div>
|
||
<div class="pe-list">
|
||
<div id="m-raw-toggle" class="pe-row" style="cursor:pointer" onclick="toggleRaw()">
|
||
<span class="pe-row-l">Éditer le fichier de configuration<span class="pe-sub">texte brut</span></span>
|
||
<span class="pe-row-c"><span id="m-raw-caret" class="pe-caret"></span></span>
|
||
</div>
|
||
<div id="m-raw-body" class="pe-row stack" style="display:none">
|
||
<textarea id="m-content" class="pe-inp" spellcheck="false"></textarea>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<!-- Pied compact, sur UNE ligne : la suppression est une icône (action rare et
|
||
destructrice, elle n'a pas à peser autant que « enregistrer »), et sa case
|
||
« supprimer aussi le .gguf » se réduit à l'extension, le libellé complet
|
||
restant en infobulle. -->
|
||
<div class="pe-foot">
|
||
<div class="foot-l">
|
||
<button id="m-del" class="pe-del" onclick="delItem()" title="supprimer" aria-label="supprimer"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M3 6h18"/><path d="M8 6V4a1 1 0 011-1h6a1 1 0 011 1v2"/><path d="M19 6l-1 14a2 2 0 01-2 2H8a2 2 0 01-2-2L5 6"/><path d="M10 11v6"/><path d="M14 11v6"/></svg></button>
|
||
</div>
|
||
<div class="foot-r">
|
||
<button class="pe-btn" onclick="closeModal()">annuler</button>
|
||
<button class="pe-btn primary" onclick="saveItem()">enregistrer</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<script src="marked.min.js"></script>
|
||
<script>
|
||
@@JS@@
|
||
</script></body></html>
|