diff --git a/cmd/jean/resource_windows_amd64.syso b/cmd/jean/resource_windows_amd64.syso
index bc0a39b..49a3bc4 100644
Binary files a/cmd/jean/resource_windows_amd64.syso and b/cmd/jean/resource_windows_amd64.syso differ
diff --git a/cmd/jean/resource_windows_arm64.syso b/cmd/jean/resource_windows_arm64.syso
index 2552e14..45c3f44 100644
Binary files a/cmd/jean/resource_windows_arm64.syso and b/cmd/jean/resource_windows_arm64.syso differ
diff --git a/cmd/jean/versioninfo.json b/cmd/jean/versioninfo.json
index 02208c7..64ff05d 100644
--- a/cmd/jean/versioninfo.json
+++ b/cmd/jean/versioninfo.json
@@ -3,13 +3,13 @@
"FileVersion": {
"Major": 0,
"Minor": 5,
- "Patch": 1,
+ "Patch": 2,
"Build": 0
},
"ProductVersion": {
"Major": 0,
"Minor": 5,
- "Patch": 1,
+ "Patch": 2,
"Build": 0
},
"FileFlagsMask": "3f",
@@ -25,7 +25,7 @@
"LegalCopyright": "Copyright (c) 2026 Jean contributors. MIT License.",
"OriginalFilename": "jean.exe",
"ProductName": "Jean",
- "ProductVersion": "0.5.1",
+ "ProductVersion": "0.5.2",
"Comments": "https://github.com/nathaninline/jean — projet open source (MIT)"
},
"VarFileInfo": {
diff --git a/internal/jean/llm_client.go b/internal/jean/llm_client.go
index 88bd1aa..9ed4837 100644
--- a/internal/jean/llm_client.go
+++ b/internal/jean/llm_client.go
@@ -679,7 +679,15 @@ func runChat(ctx context.Context, messages []Message, temperature float64, caps
if tc.ID == "" {
tc.ID = fmt.Sprintf("call_%d_%d", iter, i)
}
- if tc.Function.Arguments == "" {
+ // Les arguments DOIVENT être du JSON valide : ils sont rangés dans
+ // l'historique vu par le modèle, et le template de chat de llama.cpp les
+ // re-parse à CHAQUE requête suivante. Un modèle très quantifié peut sortir
+ // des arguments vides OU tronqués/non-JSON (ex: `{"command":"python3 …`) ;
+ // stockés tels quels, ils font échouer le parsing du template → 500 en
+ // boucle jusqu'au reset. On neutralise tout ce qui n'est pas du JSON valide
+ // en objet vide (l'appel a de toute façon déjà été exécuté). json.Valid("")
+ // étant faux, ça couvre aussi le cas vide d'origine.
+ if !json.Valid([]byte(tc.Function.Arguments)) {
tc.Function.Arguments = "{}"
}
tcs = append(tcs, tc)
diff --git a/internal/jean/run.go b/internal/jean/run.go
index 8fc0d61..c8712a8 100644
--- a/internal/jean/run.go
+++ b/internal/jean/run.go
@@ -10,7 +10,7 @@ import (
"strings"
)
-const Version = "0.5.1"
+const Version = "0.5.2"
// Main est le vrai main() du binaire (cmd/jean ne fait que l'appeler).
func Main() {
diff --git a/internal/jean/ui/index.html b/internal/jean/ui/index.html
index d4f3233..45c91c6 100644
--- a/internal/jean/ui/index.html
+++ b/internal/jean/ui/index.html
@@ -378,6 +378,12 @@ button:disabled{opacity:.5;cursor:not-allowed}
.mcp-tool-row:hover{background:var(--panel)}
.mcp-tool-switch{flex:none;transform:scale(.85)}
.mcp-tool-name{font-size:12px;color:var(--text);word-break:break-all}
+
+/* Accès OpenAI / Accès distant : champs lecture seule (URL, clé) + libellés
+ compacts, pour une présentation homogène et sans surcharge visuelle. */
+.io-cap{color:var(--dim);font-size:11px;margin:0 0 3px}
+.io-input{flex:1;min-width:0;background:var(--panel);color:var(--text);border:1px solid var(--border);border-radius:6px;padding:6px;font:inherit;font-size:12px}
+.io-group{margin-top:12px}
Endpoint compatible OpenAI?Branche n'importe quel client compatible OpenAI (OpenCode, Continue, Cursor…) sur cette URL. Modèle : jean. Si une clé est définie, le client doit envoyer Authorization: Bearer <clé>.
-
-
-
🌍 public (ajean.link) — accessible partout, TLS de bout en bout jusqu'à ce serveur
+
Endpoint compatible OpenAI?Branchez n'importe quel client compatible OpenAI (OpenCode, Continue, Cursor…) sur ces adresses. Si une clé est définie, le client doit envoyer Authorization: Bearer <clé>.
+
Modèle : jean
+
+
Adresse locale (réseau)
+
+
+
+
+
+
+
+
Adresse publique — chiffrée de bout en bout, accessible partout
-
-
+
+
-
-
🏠 local (réseau)
-
-
-
-
-
-
modèle : jean
-
Clé API?Protège l'endpoint des complétions (llama-server). Indispensable avant d'exposer le serveur sur internet. Modifier la clé redémarre le service.
+
+
Clé API?Protège l'endpoint des complétions (llama-server). Indispensable avant d'exposer le serveur sur internet. Modifier la clé redémarre le service.
ajean.link?Accédez à ce serveur Jean depuis n'importe où, sans ouvrir de port, à travers le relais ajean.link. Le trafic est chiffré de bout en bout : le relais ne voit rien. Abonnement 4,80 €/mois.
-
Ouvrez l'accès distant à ce serveur en quelques clics — compte, abonnement et connexion, sans terminal.
-
+
Ouvrez l'accès distant à ce serveur en quelques clics, sans terminal.
+
-
🌍 votre adresse d'accès distant
+
Votre adresse d'accès distant
-
+
Première connexion?À votre première ouverture du portail distant, confirmez l'empreinte et saisissez le code d'appairage : cela verrouille la boîte noire sur CE serveur (anti-interception). Une seule fois par appareil.
-
+
@@ -1170,7 +1177,7 @@ function renderApiKey(d){
document.getElementById('oai-key-eye').style.display = d.set ? '' : 'none';
}
async function loadApiKey(){ renderApiKey(await jget('/api/apikey')); }
-function toggleKeyReveal(){ OAI_REVEAL=!OAI_REVEAL; const inp=document.getElementById('oai-key'); if(OAI_KEY) inp.value = OAI_REVEAL ? OAI_KEY : (OAI_KEY.slice(0,8)+'…'+OAI_KEY.slice(-4)); }
+function toggleKeyReveal(){ OAI_REVEAL=!OAI_REVEAL; const inp=document.getElementById('oai-key'); if(OAI_KEY) inp.value = OAI_REVEAL ? OAI_KEY : (OAI_KEY.slice(0,8)+'…'+OAI_KEY.slice(-4)); const eye=document.getElementById('oai-key-eye'); if(eye) eye.textContent = OAI_REVEAL ? 'masquer' : 'afficher'; }
async function apiKeyAction(action){
if(action==='clear' && !await askConfirm('Retirer la clé rend l\'endpoint OpenAI accessible SANS authentification. Le service va redémarrer.', {title:'Retirer la clé API ?', okText:'Retirer'})) return;
if(action==='generate' && OAI_KEY && !await askConfirm('Générer une nouvelle clé invalide l\'ancienne (les clients devront être mis à jour). Le service va redémarrer.', {title:'Régénérer la clé ?', okText:'Générer'})) return;
diff --git a/internal/jean/ui/src/index.tmpl.html b/internal/jean/ui/src/index.tmpl.html
index df23aa6..2cf5a27 100644
--- a/internal/jean/ui/src/index.tmpl.html
+++ b/internal/jean/ui/src/index.tmpl.html
@@ -95,27 +95,28 @@
Accès OpenAI
-
Endpoint compatible OpenAI?Branche n'importe quel client compatible OpenAI (OpenCode, Continue, Cursor…) sur cette URL. Modèle : jean. Si une clé est définie, le client doit envoyer Authorization: Bearer <clé>.
-
-
-
🌍 public (ajean.link) — accessible partout, TLS de bout en bout jusqu'à ce serveur
+
Endpoint compatible OpenAI?Branchez n'importe quel client compatible OpenAI (OpenCode, Continue, Cursor…) sur ces adresses. Si une clé est définie, le client doit envoyer Authorization: Bearer <clé>.
+
Modèle : jean
+
+
Adresse locale (réseau)
+
+
+
+
+
+
+
+
Adresse publique — chiffrée de bout en bout, accessible partout
-
-
+
+
-
-
🏠 local (réseau)
-
-
-
-
-
-
modèle : jean
-
Clé API?Protège l'endpoint des complétions (llama-server). Indispensable avant d'exposer le serveur sur internet. Modifier la clé redémarre le service.
+
+
Clé API?Protège l'endpoint des complétions (llama-server). Indispensable avant d'exposer le serveur sur internet. Modifier la clé redémarre le service.
-
-
+
+
@@ -128,20 +129,20 @@
ajean.link?Accédez à ce serveur Jean depuis n'importe où, sans ouvrir de port, à travers le relais ajean.link. Le trafic est chiffré de bout en bout : le relais ne voit rien. Abonnement 4,80 €/mois.
-
Ouvrez l'accès distant à ce serveur en quelques clics — compte, abonnement et connexion, sans terminal.
-
+
Ouvrez l'accès distant à ce serveur en quelques clics, sans terminal.
+
-
🌍 votre adresse d'accès distant
+
Votre adresse d'accès distant
-
+
Première connexion?À votre première ouverture du portail distant, confirmez l'empreinte et saisissez le code d'appairage : cela verrouille la boîte noire sur CE serveur (anti-interception). Une seule fois par appareil.
-
+
diff --git a/internal/jean/ui/src/js/06-settings.js b/internal/jean/ui/src/js/06-settings.js
index 9b031ff..f57f0df 100644
--- a/internal/jean/ui/src/js/06-settings.js
+++ b/internal/jean/ui/src/js/06-settings.js
@@ -186,7 +186,7 @@ function renderApiKey(d){
document.getElementById('oai-key-eye').style.display = d.set ? '' : 'none';
}
async function loadApiKey(){ renderApiKey(await jget('/api/apikey')); }
-function toggleKeyReveal(){ OAI_REVEAL=!OAI_REVEAL; const inp=document.getElementById('oai-key'); if(OAI_KEY) inp.value = OAI_REVEAL ? OAI_KEY : (OAI_KEY.slice(0,8)+'…'+OAI_KEY.slice(-4)); }
+function toggleKeyReveal(){ OAI_REVEAL=!OAI_REVEAL; const inp=document.getElementById('oai-key'); if(OAI_KEY) inp.value = OAI_REVEAL ? OAI_KEY : (OAI_KEY.slice(0,8)+'…'+OAI_KEY.slice(-4)); const eye=document.getElementById('oai-key-eye'); if(eye) eye.textContent = OAI_REVEAL ? 'masquer' : 'afficher'; }
async function apiKeyAction(action){
if(action==='clear' && !await askConfirm('Retirer la clé rend l\'endpoint OpenAI accessible SANS authentification. Le service va redémarrer.', {title:'Retirer la clé API ?', okText:'Retirer'})) return;
if(action==='generate' && OAI_KEY && !await askConfirm('Générer une nouvelle clé invalide l\'ancienne (les clients devront être mis à jour). Le service va redémarrer.', {title:'Régénérer la clé ?', okText:'Générer'})) return;
diff --git a/internal/jean/ui/src/styles.css b/internal/jean/ui/src/styles.css
index 3b56404..87d6148 100644
--- a/internal/jean/ui/src/styles.css
+++ b/internal/jean/ui/src/styles.css
@@ -359,3 +359,9 @@ button:disabled{opacity:.5;cursor:not-allowed}
.mcp-tool-row:hover{background:var(--panel)}
.mcp-tool-switch{flex:none;transform:scale(.85)}
.mcp-tool-name{font-size:12px;color:var(--text);word-break:break-all}
+
+/* Accès OpenAI / Accès distant : champs lecture seule (URL, clé) + libellés
+ compacts, pour une présentation homogène et sans surcharge visuelle. */
+.io-cap{color:var(--dim);font-size:11px;margin:0 0 3px}
+.io-input{flex:1;min-width:0;background:var(--panel);color:var(--text);border:1px solid var(--border);border-radius:6px;padding:6px;font:inherit;font-size:12px}
+.io-group{margin-top:12px}