feat: section Profil (langue) + ghost text lie a la langue
CI / lint (push) Failing after 10s
CI / test (push) Has been skipped
CI / build (push) Has been skipped
CI / e2e (push) Has been skipped
CI / security (push) Failing after -4s

- Section Profil dans Configuration: langue (fr/en/es/de), nom
- Persistance localStorage (obsigate-lang, obsigate-name)
- Ghost text adapte le prompt a la langue du profil
- CSS profile-form, config-select stylise
This commit is contained in:
2026-06-16 12:14:05 -04:00
parent 2d03586ae0
commit 0a0cecf178
4 changed files with 86 additions and 4 deletions
+9 -4
View File
@@ -1822,19 +1822,24 @@ body { font-family: var(--sans); background: var(--bg); color: var(--text); heig
var lastChar = fullText.slice(-1);
var midWord = /[a-zA-Z0-9\u00C0-\u024F]$/.test(lastChar);
// Get user's preferred language
var lang = 'fr';
try { lang = localStorage.getItem('obsigate-lang') || 'fr'; } catch(e) {}
var langNames = { fr: 'French', en: 'English', es: 'Spanish', de: 'German' };
var langName = langNames[lang] || 'French';
var prompt, inputText;
if (midWord) {
var wordMatch = fullText.match(/([\w\u00C0-\u024F]+)$/);
var partialWord = wordMatch ? wordMatch[1] : fullText;
inputText = partialWord;
// Provide surrounding context so the model can guess the word
var context = fullText.slice(0, -partialWord.length).trim();
prompt = 'Complete this French word. The word is: "' + partialWord +
prompt = 'Complete this ' + langName + ' word. The word is: "' + partialWord +
'". Context: "' + (context || '(start of line)') +
'". Return ONLY the remaining letters of the word. Example: if word is "famil" return "ial" for "familial". Do NOT add spaces.';
'". Return ONLY the remaining letters. Do NOT add spaces. Example: "famil" → "ial" for "familial".';
} else {
inputText = fullText;
prompt = 'Continue this text. Return ONLY the new text (do NOT repeat what is already written):\n' + fullText;
prompt = 'Continue this ' + langName + ' text naturally. Return ONLY the new text (do NOT repeat):\n' + fullText;
}
fetch('/api/ai/inline-complete', {
+35
View File
@@ -1348,6 +1348,11 @@
>🤖 IA</a
>
</li>
<li>
<a href="#cfg-profile" class="help-nav-link"
>👤 Profil</a
>
</li>
<li>
<a href="#cfg-about" class="help-nav-link"
>📦 A propos</a
@@ -1962,6 +1967,36 @@
</div>
</section>
<!-- Profil -->
<section
class="config-section help-section"
id="cfg-profile"
>
<h2>👤 Profil utilisateur</h2>
<p class="config-description">
Preferences personnelles pour personnaliser
l'experience ObsiGate.
</p>
<div class="profile-form" id="profile-form">
<div class="profile-field">
<label for="profile-lang">Langue preferee</label>
<select id="profile-lang" class="config-select">
<option value="fr">Francais</option>
<option value="en">English</option>
<option value="es">Espanol</option>
<option value="de">Deutsch</option>
</select>
<span class="profile-hint">Utilisee par l'autocompletion et le ghost text.</span>
</div>
<div class="profile-field">
<label for="profile-name">Nom d'affichage</label>
<input type="text" id="profile-name" class="config-input" placeholder="Votre nom" maxlength="60">
</div>
<button class="config-save-btn" id="profile-save">Enregistrer</button>
<span class="profile-saved" id="profile-saved" style="display:none">✓ Sauvegarde</span>
</div>
</section>
<!-- À propos -->
<section
class="config-section help-section"
+29
View File
@@ -644,6 +644,9 @@ function initConfigModal() {
// Init theme picker
initThemePicker();
// Init profile
initProfile();
}
function closeConfigModal() {
@@ -1387,6 +1390,7 @@ export {
loadHiddenFilesSettings as initHiddenFilesConfig,
initThemePicker,
initAboutModal,
initProfile,
switchSidebarTab,
};
@@ -1508,3 +1512,28 @@ function initAboutModal() {
if (jokeEl) jokeEl.textContent = '« ' + jokes[Math.floor(Math.random() * jokes.length)] + ' »';
}
}
// ── Profile ──────────────────────────────────────────────────────────
function initProfile() {
var langSel = document.getElementById('profile-lang');
var nameInp = document.getElementById('profile-name');
var saveBtn = document.getElementById('profile-save');
var savedEl = document.getElementById('profile-saved');
if (!langSel || !saveBtn) return;
// Load saved profile
try {
var lang = localStorage.getItem('obsigate-lang') || 'fr';
var name = localStorage.getItem('obsigate-name') || '';
if (langSel) langSel.value = lang;
if (nameInp) nameInp.value = name;
} catch(e) {}
saveBtn.addEventListener('click', function() {
try {
localStorage.setItem('obsigate-lang', langSel.value);
if (nameInp) localStorage.setItem('obsigate-name', nameInp.value.trim());
if (savedEl) { savedEl.style.display = 'inline'; setTimeout(function() { savedEl.style.display = 'none'; }, 2000); }
} catch(e) {}
});
}
+13
View File
@@ -2215,6 +2215,19 @@ select {
background: var(--accent);
color: #fff;
}
/* Profile section */
.profile-form { max-width: 420px; }
.profile-field { margin-bottom: 14px; }
.profile-field label { display: block; font-size: 0.75rem; font-weight: 600; color: var(--text-primary); margin-bottom: 4px; }
.profile-field .config-input,
.profile-field .config-select {
width: 100%; padding: 6px 10px; font-size: 0.8rem;
background: var(--bg-primary); color: var(--text-primary);
border: 1px solid var(--border); border-radius: 6px;
font-family: inherit;
}
.profile-hint { display: block; font-size: 0.65rem; color: var(--text-muted); margin-top: 4px; }
.profile-saved { font-size: 0.7rem; color: var(--accent-green); margin-left: 10px; }
.saved-search-vault {
font-size: 0.65rem;
color: var(--text-muted);