feat: section Profil (langue) + ghost text lie a la langue
- 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:
@@ -1822,19 +1822,24 @@ body { font-family: var(--sans); background: var(--bg); color: var(--text); heig
|
|||||||
var lastChar = fullText.slice(-1);
|
var lastChar = fullText.slice(-1);
|
||||||
var midWord = /[a-zA-Z0-9\u00C0-\u024F]$/.test(lastChar);
|
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;
|
var prompt, inputText;
|
||||||
if (midWord) {
|
if (midWord) {
|
||||||
var wordMatch = fullText.match(/([\w\u00C0-\u024F]+)$/);
|
var wordMatch = fullText.match(/([\w\u00C0-\u024F]+)$/);
|
||||||
var partialWord = wordMatch ? wordMatch[1] : fullText;
|
var partialWord = wordMatch ? wordMatch[1] : fullText;
|
||||||
inputText = partialWord;
|
inputText = partialWord;
|
||||||
// Provide surrounding context so the model can guess the word
|
|
||||||
var context = fullText.slice(0, -partialWord.length).trim();
|
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)') +
|
'". 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 {
|
} else {
|
||||||
inputText = fullText;
|
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', {
|
fetch('/api/ai/inline-complete', {
|
||||||
|
|||||||
@@ -1348,6 +1348,11 @@
|
|||||||
>🤖 IA</a
|
>🤖 IA</a
|
||||||
>
|
>
|
||||||
</li>
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="#cfg-profile" class="help-nav-link"
|
||||||
|
>👤 Profil</a
|
||||||
|
>
|
||||||
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href="#cfg-about" class="help-nav-link"
|
<a href="#cfg-about" class="help-nav-link"
|
||||||
>📦 A propos</a
|
>📦 A propos</a
|
||||||
@@ -1962,6 +1967,36 @@
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</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 -->
|
<!-- À propos -->
|
||||||
<section
|
<section
|
||||||
class="config-section help-section"
|
class="config-section help-section"
|
||||||
|
|||||||
@@ -644,6 +644,9 @@ function initConfigModal() {
|
|||||||
|
|
||||||
// Init theme picker
|
// Init theme picker
|
||||||
initThemePicker();
|
initThemePicker();
|
||||||
|
|
||||||
|
// Init profile
|
||||||
|
initProfile();
|
||||||
}
|
}
|
||||||
|
|
||||||
function closeConfigModal() {
|
function closeConfigModal() {
|
||||||
@@ -1387,6 +1390,7 @@ export {
|
|||||||
loadHiddenFilesSettings as initHiddenFilesConfig,
|
loadHiddenFilesSettings as initHiddenFilesConfig,
|
||||||
initThemePicker,
|
initThemePicker,
|
||||||
initAboutModal,
|
initAboutModal,
|
||||||
|
initProfile,
|
||||||
switchSidebarTab,
|
switchSidebarTab,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1508,3 +1512,28 @@ function initAboutModal() {
|
|||||||
if (jokeEl) jokeEl.textContent = '« ' + jokes[Math.floor(Math.random() * jokes.length)] + ' »';
|
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) {}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
@@ -2215,6 +2215,19 @@ select {
|
|||||||
background: var(--accent);
|
background: var(--accent);
|
||||||
color: #fff;
|
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 {
|
.saved-search-vault {
|
||||||
font-size: 0.65rem;
|
font-size: 0.65rem;
|
||||||
color: var(--text-muted);
|
color: var(--text-muted);
|
||||||
|
|||||||
Reference in New Issue
Block a user