mirror of
https://github.com/R0m1k3/Loki.git
synced 2026-10-18 12:46:56 +02:00
- module github.com/R0m1k3/Loki, cmd/loki, internal/loki (package loki) - LOKI_HOME, LOKI_MODEL_DIRS, LOKI_SERVICE, LOKI_DL_CONNS ; /etc/loki ; units loki-engine / loki-ui ; binaire et aide CLI - updateRepo pointe sur R0m1k3/Loki (l'auto-update ne tirera plus les binaires AJEAN amont) Conservé à l'identique : le domaine ajean.link (service de tunnel amont), les littéraux de migration 0.7.x (migrate_07.go), RELEASE_NOTES.md et LICENSE (historique et licence de l'amont). go build/vet/test : verts.
69 lines
3.1 KiB
JavaScript
69 lines
3.1 KiB
JavaScript
// marked: GFM on, no auto-breaks (single newlines stay inline).
|
|
marked.setOptions({ gfm: true, breaks: false });
|
|
// Pre-process to fix common LLM markdown mistakes before handing to marked:
|
|
// - code fences glued to preceding text on the same line ("foo ```ruby ...")
|
|
// - fences without a closing newline
|
|
// - 3+ consecutive blank lines collapsed to one (LLMs love padding)
|
|
function fixMd(s){
|
|
if(!s) return '';
|
|
// Force a newline before a fence that's been glued to preceding text
|
|
// (common LLM mistake: "...crée le fichier et```ruby"). We do NOT split
|
|
// *after* the fence — the part after the fence is the language identifier.
|
|
s = s.replace(/([^\n])(\s*)(```+|~~~+)(?=\w*\s*\n)/g, '$1\n$3');
|
|
// Collapse runs of blank lines that LLMs love to emit.
|
|
s = s.replace(/\n{3,}/g, '\n\n');
|
|
return s;
|
|
}
|
|
function md(src){ return src ? marked.parse(fixMd(src)) : ''; }
|
|
// Les garde-fous du serveur ("[stop: trop d'appels d'outils]") sont concaténés
|
|
// au texte de la réponse : ils arrivent donc en clair, au milieu du markdown.
|
|
// markNotices les sort du fil pour qu'on ne les prenne pas pour une phrase du
|
|
// modèle. En cours de streaming, la parenthèse fermante manque encore → aucune
|
|
// correspondance, donc pas d'encart qui clignote à chaque token.
|
|
const NOTICE_RE=/\[stop\s*:\s*([^\]]+)\]/g;
|
|
function markNotices(root){
|
|
const walk=document.createTreeWalker(root, NodeFilter.SHOW_TEXT);
|
|
const hits=[];
|
|
while(walk.nextNode()){
|
|
const n=walk.currentNode;
|
|
// Un exemple dans un bloc de code reste du code.
|
|
if(n.parentElement && n.parentElement.closest('pre,code')) continue;
|
|
NOTICE_RE.lastIndex=0;
|
|
if(NOTICE_RE.test(n.nodeValue)) hits.push(n);
|
|
}
|
|
hits.forEach(n=>{
|
|
const frag=document.createDocumentFragment();
|
|
let rest=n.nodeValue, m;
|
|
NOTICE_RE.lastIndex=0;
|
|
while((m=NOTICE_RE.exec(rest))!==null){
|
|
if(m.index) frag.appendChild(document.createTextNode(rest.slice(0,m.index)));
|
|
const tag=document.createElement('span');
|
|
tag.className='stopnote'; tag.textContent=m[1].trim();
|
|
frag.appendChild(tag);
|
|
rest=rest.slice(m.index+m[0].length);
|
|
NOTICE_RE.lastIndex=0;
|
|
}
|
|
if(rest) frag.appendChild(document.createTextNode(rest));
|
|
n.parentNode.replaceChild(frag, n);
|
|
});
|
|
}
|
|
let msgs = [];
|
|
let busy = false;
|
|
function toggleSide(){ document.getElementById('side').classList.toggle('open'); document.getElementById('backdrop').classList.toggle('open'); document.body.classList.toggle('drawer-open'); }
|
|
// Prompt système : persisté CÔTÉ SERVEUR (/api/sysprompt, partagé entre
|
|
// appareils, utilisé par la génération serveur). localStorage = simple cache
|
|
// d'affichage hors-ligne. Debounce : on n'écrit qu'après une pause de frappe.
|
|
let _sysT = null;
|
|
function saveSys(){
|
|
const v = document.getElementById('sysprompt').value;
|
|
localStorage.setItem('loki.sys', v);
|
|
clearTimeout(_sysT);
|
|
_sysT = setTimeout(()=>{ jpost('/api/sysprompt', {text:v}).catch(()=>{}); }, 600);
|
|
}
|
|
async function loadSys(){
|
|
try{
|
|
const d = await jget('/api/sysprompt');
|
|
if(d && d.ok){ document.getElementById('sysprompt').value = d.text || ''; localStorage.setItem('loki.sys', d.text || ''); }
|
|
}catch(e){}
|
|
}
|