feat: per-user feedback AI prompt template with placeholder keys

This commit is contained in:
dev
2026-10-06 18:55:04 +03:00
parent 285ee84c98
commit a73b1b3f8c
8 changed files with 269 additions and 8 deletions
+81 -1
View File
@@ -1,7 +1,7 @@
(async function() {
const API = location.origin;
const PAGE_SIZE = 20;
let page = 1, total = 0, searchTimer, groupsLoaded, feedbacks = [], shortsListCache = null, shortsFetchTimer = null, lastFbAutoText = '';
let page = 1, total = 0, searchTimer, groupsLoaded, feedbacks = [], shortsListCache = null, shortsFetchTimer = null, lastFbAutoText = '', userSettings = { settings: {}, defaults: {} }, aiActive = 'native';
const topicReportCache = new Map();
function hdr() { const t = sessionStorage.getItem('authToken'); return t ? { 'X-Auth-Token': t } : {}; }
@@ -68,6 +68,69 @@
function closeModal() {
const m = document.getElementById('feedbackModal');
if (m) m.classList.remove('open');
closePromptModal();
}
function closePromptModal() {
const m = document.getElementById('fbPromptModal');
if (m) m.classList.remove('open');
}
function updatePromptCount() {
const el = document.getElementById('fbPromptCount');
const ta = document.getElementById('fbPromptText');
if (el && ta) el.textContent = `${ta.value.length} / 8000`;
}
async function loadUserSettings() {
try {
const r = await fetch(`${API}/api/user-settings`, { headers: hdr() });
if (!r.ok) return;
const j = await r.json();
userSettings.settings = j.settings || {};
userSettings.defaults = j.defaults || {};
} catch (e) {}
}
function openPromptModal() {
const src = document.getElementById('fbModel');
const dst = document.getElementById('fbDefaultModel');
dst.innerHTML = '<option value="">Активная модель по умолчанию</option>' + src.innerHTML;
dst.value = userSettings.settings.feedback_profile || '';
const ta = document.getElementById('fbPromptText');
ta.value = userSettings.settings.feedback_prompt || '';
ta.placeholder = userSettings.defaults.feedback_prompt || 'Системный шаблон';
document.getElementById('fbPromptStatus').hidden = true;
updatePromptCount();
document.getElementById('fbPromptModal').classList.add('open');
if (window.lucide) lucide.createIcons();
}
async function savePromptSettings() {
const st = document.getElementById('fbPromptStatus');
const body = {
settings: {
feedback_prompt: document.getElementById('fbPromptText').value.trim(),
feedback_profile: document.getElementById('fbDefaultModel').value || '',
}
};
try {
const r = await fetch(`${API}/api/user-settings`, { method: 'PUT', headers: {...hdr(), 'Content-Type': 'application/json'}, body: JSON.stringify(body) });
const j = await r.json().catch(() => ({}));
if (!r.ok) {
st.hidden = false;
st.innerHTML = `<span class="ai-badge error">${escapeHtml(j.error || 'Не удалось сохранить')}</span>`;
return;
}
userSettings.settings = j.settings || {};
userSettings.defaults = j.defaults || userSettings.defaults;
st.hidden = false;
st.innerHTML = '<span class="ai-badge done">Сохранено</span>';
setTimeout(closePromptModal, 700);
} catch (e) {
st.hidden = false;
st.innerHTML = '<span class="ai-badge error">Не удалось сохранить</span>';
}
}
function todayIso() {
@@ -170,6 +233,7 @@
const nativeOpt = document.createElement('option'); nativeOpt.value = 'native'; nativeOpt.textContent = 'Нативная (llama.cpp)'; sel.appendChild(nativeOpt);
(d.profiles||[]).forEach(p => { const o = document.createElement('option'); o.value = p.id; o.textContent = p.name; sel.appendChild(o); });
if (d.active) sel.value = d.active;
aiActive = d.active || 'native';
}
} catch (e) {}
}
@@ -226,6 +290,10 @@
document.getElementById('fbTextHint').textContent = '0 / 5000';
document.getElementById('fbAiCheck').checked = true;
document.getElementById('fbAiStatus').hidden = true;
const ms = document.getElementById('fbModel');
const pref = userSettings.settings.feedback_profile;
if (pref && [...ms.options].some(o => o.value === pref)) ms.value = pref;
else if (aiActive && [...ms.options].some(o => o.value === aiActive)) ms.value = aiActive;
document.getElementById('fbModalTitle').textContent = 'Добавить отзыв';
document.getElementById('fbSubmit').textContent = 'Отправить в очередь';
}
@@ -309,6 +377,7 @@
await checkAuth().catch(()=>{});
buildSidebar('feedback');
await loadGroups();
await loadUserSettings();
await loadAiProfiles();
await loadResidentNames();
loadTopics();
@@ -316,6 +385,17 @@
connectFeedbackLive();
document.getElementById('addFeedbackBtn').addEventListener('click', () => openModal({}));
document.getElementById('fbCancel').addEventListener('click', closeModal);
document.getElementById('fbPromptBtn').addEventListener('click', openPromptModal);
document.getElementById('fbPromptClose').addEventListener('click', closePromptModal);
document.getElementById('fbPromptModal').addEventListener('click', e => { if (e.target.id === 'fbPromptModal') closePromptModal(); });
document.getElementById('fbPromptText').addEventListener('input', updatePromptCount);
document.getElementById('fbPromptReset').addEventListener('click', () => {
document.getElementById('fbPromptText').value = '';
document.getElementById('fbDefaultModel').value = '';
document.getElementById('fbPromptStatus').hidden = true;
updatePromptCount();
});
document.getElementById('fbPromptSave').addEventListener('click', savePromptSettings);
document.getElementById('feedbackModal').addEventListener('click', e => { if (e.target.id === 'feedbackModal') closeModal(); });
document.getElementById('fbForm').addEventListener('submit', async e => {
e.preventDefault();