diff --git a/ui/index.html b/ui/index.html
index 0b65f84..2c04d25 100644
--- a/ui/index.html
+++ b/ui/index.html
@@ -757,8 +757,11 @@ function copyApiKey(){ copyText(OAI_KEY, OAI_KEY?'clé copiée':'aucune clé');
function renderApiKey(d){
OAI_KEY = d.key || '';
// URL de l'endpoint : llama-server tourne sur d.port (≠ port de l'UI web).
- const proto = location.protocol==='https:' ? 'https:' : 'http:';
- document.getElementById('oai-url').value = proto+'//'+location.hostname+':'+d.port+'/v1';
+ // On prend l'hôte annoncé par le serveur (IP LAN détectée côté Go) : dans le
+ // tunnel ajean.link, location.hostname serait le domaine du relais (faux) —
+ // l'accès OpenAI reste TOUJOURS l'adresse locale de la machine.
+ const host = d.host || location.hostname;
+ document.getElementById('oai-url').value = 'http://'+host+':'+d.port+'/v1';
const inp=document.getElementById('oai-key');
if(!d.set){ inp.value='(aucune clé — serveur ouvert)'; inp.style.opacity=.6; }
else { inp.style.opacity=1; inp.value = OAI_REVEAL ? OAI_KEY : d.masked; }
diff --git a/web.go b/web.go
index 4874c95..eefe73a 100644
--- a/web.go
+++ b/web.go
@@ -547,9 +547,26 @@ func handleAPIKey(w http.ResponseWriter, r *http.Request) {
"key": k,
"masked": maskAPIKey(k),
"port": LLMPort(),
+ "host": localIP(),
})
}
+// localIP best-effort renvoie l'IPv4 LAN primaire de la machine (l'IP source du
+// trafic sortant), ou "localhost" à défaut. Sert à annoncer l'endpoint OpenAI
+// avec une adresse correcte sur le réseau local MÊME quand l'UI est atteinte via
+// le tunnel ajean.link (où location.hostname serait le domaine du relais, faux).
+func localIP() string {
+ conn, err := net.Dial("udp", "8.8.8.8:80")
+ if err != nil {
+ return "localhost"
+ }
+ defer conn.Close()
+ if a, ok := conn.LocalAddr().(*net.UDPAddr); ok && a.IP != nil {
+ return a.IP.String()
+ }
+ return "localhost"
+}
+
func handleInternet(w http.ResponseWriter, r *http.Request) {
if r.Method == http.MethodPost {
var req struct {