refactor(settings): AI profile add/edit moved into a modal dialog (overlay click + Esc to close)
This commit is contained in:
+11
-3
@@ -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
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user