refactor(settings): AI profile add/edit moved into a modal dialog (overlay click + Esc to close)

This commit is contained in:
dev
2026-09-17 12:00:19 +03:00
parent d7c793c67a
commit 66f7fdc1a8
2 changed files with 40 additions and 31 deletions
+11 -3
View File
@@ -180,7 +180,8 @@ let aiProfileEditingId = null;
function openAiProfileForm(profile) { function openAiProfileForm(profile) {
aiProfileEditingId = profile ? profile.id : null; aiProfileEditingId = profile ? profile.id : null;
document.getElementById('aiProfileForm').style.display = 'block'; document.getElementById('aiProfileModalTitle').textContent = profile ? 'Изменить профиль ИИ' : 'Добавить профиль ИИ';
document.getElementById('aiProfileModal').classList.add('open');
document.getElementById('aiProfileName').value = profile ? profile.name : ''; document.getElementById('aiProfileName').value = profile ? profile.name : '';
document.getElementById('aiProfileModel').value = profile ? profile.model : ''; document.getElementById('aiProfileModel').value = profile ? profile.model : '';
document.getElementById('aiProfileBaseUrl').value = profile ? profile.base_url : ''; document.getElementById('aiProfileBaseUrl').value = profile ? profile.base_url : '';
@@ -191,7 +192,7 @@ function openAiProfileForm(profile) {
function closeAiProfileForm() { function closeAiProfileForm() {
aiProfileEditingId = null; aiProfileEditingId = null;
document.getElementById('aiProfileForm').style.display = 'none'; document.getElementById('aiProfileModal').classList.remove('open');
} }
function aiProfileFormData() { function aiProfileFormData() {
@@ -218,18 +219,21 @@ async function loadAiQueue() {
async function saveAiProfile() { async function saveAiProfile() {
const body = aiProfileFormData(); const body = aiProfileFormData();
const st = document.getElementById('aiProfileTestStatus');
st.innerHTML = '<span class="spinner dark"></span> Сохранение...';
const res = await fetch(aiProfileEditingId ? `${API}/api/ai/profiles/${aiProfileEditingId}` : `${API}/api/ai/profiles`, { const res = await fetch(aiProfileEditingId ? `${API}/api/ai/profiles/${aiProfileEditingId}` : `${API}/api/ai/profiles`, {
method: aiProfileEditingId ? 'PUT' : 'POST', method: aiProfileEditingId ? 'PUT' : 'POST',
headers: hdrJson(), headers: hdrJson(),
body: JSON.stringify(body), body: JSON.stringify(body),
}); });
if (res.ok) { if (res.ok) {
st.textContent = '';
showToast(aiProfileEditingId ? 'Профиль обновлён' : 'Профиль добавлен'); showToast(aiProfileEditingId ? 'Профиль обновлён' : 'Профиль добавлен');
closeAiProfileForm(); closeAiProfileForm();
loadAiProfiles(); loadAiProfiles();
} else { } else {
const err = await res.json().catch(() => ({})); const err = await res.json().catch(() => ({}));
alert(err.error || 'Ошибка сохранения'); st.innerHTML = `<span style="color:#ef4444;font-weight:600">${esc(err.error || 'Ошибка сохранения')}</span>`;
} }
} }
@@ -271,6 +275,10 @@ document.getElementById('aiProfileAddBtn').addEventListener('click', () => openA
document.getElementById('aiProfileSaveBtn').addEventListener('click', saveAiProfile); document.getElementById('aiProfileSaveBtn').addEventListener('click', saveAiProfile);
document.getElementById('aiProfileTestBtn').addEventListener('click', testAiProfile); document.getElementById('aiProfileTestBtn').addEventListener('click', testAiProfile);
document.getElementById('aiProfileCancelBtn').addEventListener('click', closeAiProfileForm); document.getElementById('aiProfileCancelBtn').addEventListener('click', closeAiProfileForm);
document.getElementById('aiProfileModal').addEventListener('click', e => { if (e.target === e.currentTarget) closeAiProfileForm(); });
document.addEventListener('keydown', e => {
if (e.key === 'Escape' && document.getElementById('aiProfileModal').classList.contains('open')) closeAiProfileForm();
});
document.getElementById('aiActiveProfile').addEventListener('change', e => activateAiProfile(e.target.value)); document.getElementById('aiActiveProfile').addEventListener('change', e => activateAiProfile(e.target.value));
document.getElementById('aiProfilesList').addEventListener('click', e => { document.getElementById('aiProfilesList').addEventListener('click', e => {
const del = e.target.closest('[data-del-profile]'); const del = e.target.closest('[data-del-profile]');
+29 -28
View File
@@ -147,34 +147,6 @@
<button class="btn-primary ghost" id="aiProfileAddBtn" type="button"><i data-lucide="plus"></i> Добавить профиль</button> <button class="btn-primary ghost" id="aiProfileAddBtn" type="button"><i data-lucide="plus"></i> Добавить профиль</button>
</div> </div>
<div id="aiProfilesList" class="ai-profiles-list"></div> <div id="aiProfilesList" class="ai-profiles-list"></div>
<div id="aiProfileForm" style="display:none;margin-top:14px;border:1px solid var(--border);border-radius:var(--radius);padding:16px">
<div class="settings-row">
<div class="settings-field">
<label for="aiProfileName">Название профиля</label>
<input type="text" id="aiProfileName" class="settings-input" placeholder="Например: OpenRouter GPT-4o-mini">
</div>
<div class="settings-field">
<label for="aiProfileModel">Модель</label>
<input type="text" id="aiProfileModel" class="settings-input" placeholder="openai/gpt-4o-mini">
</div>
</div>
<div class="settings-row">
<div class="settings-field" style="flex:1">
<label for="aiProfileBaseUrl">Base URL (OpenAI-совместимый API)</label>
<input type="text" id="aiProfileBaseUrl" class="settings-input" placeholder="https://openrouter.ai/api/v1">
</div>
<div class="settings-field">
<label for="aiProfileApiKey">API-ключ (если требуется)</label>
<input type="password" id="aiProfileApiKey" class="settings-input" placeholder="sk-...">
</div>
</div>
<div style="display:flex;gap:10px;margin-top:12px;flex-wrap:wrap">
<button class="btn-primary" id="aiProfileSaveBtn" type="button">Сохранить профиль</button>
<button class="btn-primary ghost" id="aiProfileTestBtn" type="button"><i data-lucide="plug-zap"></i> Проверить</button>
<button class="btn-primary ghost" id="aiProfileCancelBtn" type="button">Отмена</button>
<span id="aiProfileTestStatus" style="font-size:.85rem;align-self:center"></span>
</div>
</div>
</div> </div>
<div class="settings-card"> <div class="settings-card">
@@ -208,6 +180,35 @@
<span class="hint" id="saveHint" style="display:inline-flex;align-items:center;gap:6px"><i data-lucide="triangle-alert"></i> Есть несохранённые изменения</span> <span class="hint" id="saveHint" style="display:inline-flex;align-items:center;gap:6px"><i data-lucide="triangle-alert"></i> Есть несохранённые изменения</span>
<button class="btn-primary" id="saveBtn" type="button">Сохранить изменения</button> <button class="btn-primary" id="saveBtn" type="button">Сохранить изменения</button>
</div> </div>
<div class="modal-overlay" id="aiProfileModal">
<div class="edit-modal" style="max-width:560px">
<h3 id="aiProfileModalTitle">Добавить профиль ИИ</h3>
<div class="settings-field">
<label for="aiProfileName">Название профиля <span style="color:#ef4444">*</span></label>
<input type="text" id="aiProfileName" class="settings-input" placeholder="Например: OpenRouter GPT-4o-mini">
</div>
<div class="settings-field">
<label for="aiProfileModel">Модель <span style="color:#ef4444">*</span></label>
<input type="text" id="aiProfileModel" class="settings-input" placeholder="openai/gpt-4o-mini">
</div>
<div class="settings-field">
<label for="aiProfileBaseUrl">Base URL (OpenAI-совместимый API) <span style="color:#ef4444">*</span></label>
<input type="text" id="aiProfileBaseUrl" class="settings-input" placeholder="https://openrouter.ai/api/v1">
</div>
<div class="settings-field">
<label for="aiProfileApiKey">API-ключ (если требуется)</label>
<input type="password" id="aiProfileApiKey" class="settings-input" placeholder="sk-...">
</div>
<span id="aiProfileTestStatus" style="font-size:.85rem;min-height:1.2em"></span>
<div class="actions" style="flex-wrap:wrap">
<button class="btn-primary" id="aiProfileSaveBtn" type="button">Сохранить</button>
<button class="btn-primary ghost" id="aiProfileTestBtn" type="button"><i data-lucide="plug-zap"></i> Проверить</button>
<button class="btn-primary ghost" id="aiProfileCancelBtn" type="button">Отмена</button>
</div>
</div>
</div>
<div class="toast" id="toast"></div> <div class="toast" id="toast"></div>
<script src="vendor/lucide.min.js"></script> <script src="vendor/lucide.min.js"></script>
<script src="admin.js"></script> <script src="admin.js"></script>