Changeset 0.15.0 (#71)

This commit is contained in:
2026-02-26 21:19:55 +00:00
parent e2149e249d
commit 2d79ff593b
35 changed files with 3218 additions and 504 deletions

View File

@@ -52,6 +52,26 @@ async function summarizeAndContinue() {
}
}
// ── Per-Channel Auto-Compaction Toggle ──────
async function toggleChannelAutoCompact(enabled) {
const chatId = App.currentChatId;
if (!chatId) return;
const chat = App.chats.find(c => c.id === chatId);
if (!chat) return;
// Update local state
if (!chat.settings) chat.settings = {};
chat.settings.auto_compaction = enabled;
// Persist to server (JSONB merge)
try {
await API.updateChannel(chatId, { settings: { auto_compaction: enabled } });
} catch (e) {
console.warn('Failed to save auto-compaction setting:', e.message);
}
}
// ── Chat Management ──────────────────────────
async function loadChats() {

View File

@@ -58,13 +58,36 @@ function updateAvatarPreview(dataURI) {
// ── Settings Handlers ────────────────────────
function handleSaveSettings() {
async function handleSaveSettings() {
App.settings.model = document.getElementById('settingsModel').value || App.settings.model;
App.settings.systemPrompt = document.getElementById('settingsSystemPrompt').value.trim();
App.settings.maxTokens = parseInt(document.getElementById('settingsMaxTokens').value) || 0; // 0 = auto
App.settings.temperature = parseFloat(document.getElementById('settingsTemp').value) || 0.7;
App.settings.showThinking = document.getElementById('settingsThinking').checked;
saveSettings();
// ── Save profile fields (display name, email) ──
const displayName = document.getElementById('profileDisplayName').value.trim();
const email = document.getElementById('profileEmail').value.trim();
const profileUpdates = {};
if (displayName !== (API.user?.display_name || '')) profileUpdates.display_name = displayName;
if (email !== (API.user?.email || '')) profileUpdates.email = email;
if (Object.keys(profileUpdates).length > 0) {
try {
const updated = await API.updateProfile(profileUpdates);
// Sync local user object so sidebar/avatar reflect the change
if (updated) {
if (updated.display_name !== undefined) API.user.display_name = updated.display_name;
if (updated.email !== undefined) API.user.email = updated.email;
API.saveTokens();
UI.updateUser();
}
} catch (e) {
console.warn('Profile save failed:', e.message);
UI.toast('Profile update failed: ' + e.message, 'error');
}
}
UI.updateModelSelector();
UI.updateCapabilityBadges();
UI.closeSettings();
@@ -204,6 +227,20 @@ async function handleSaveAdminSettings() {
};
await API.adminUpdateSetting('search_config', { value: searchConfig });
// Auto-Compaction config → global_settings
const compactionEnabled = document.getElementById('adminCompactionEnabled')?.checked || false;
const compactionThreshold = parseInt(document.getElementById('adminCompactionThreshold')?.value) || 70;
const compactionCooldown = parseInt(document.getElementById('adminCompactionCooldown')?.value) || 30;
await API.adminUpdateSetting('auto_compaction', { value: {
enabled: compactionEnabled,
threshold: compactionThreshold,
cooldown: compactionCooldown,
}});
// Scanner reads these individual keys each tick
await API.adminUpdateSetting('auto_compaction_enabled', { value: compactionEnabled });
await API.adminUpdateSetting('auto_compaction_threshold', { value: compactionThreshold / 100 });
await API.adminUpdateSetting('auto_compaction_cooldown_minutes', { value: compactionCooldown });
UI.toast('Settings saved', 'success');
// Live-apply: refresh policies and dependent UI
await initBanners();
@@ -233,6 +270,9 @@ function _initUserRolePrimitive() {
return list.filter(c => c.scope === 'personal');
},
fetchModels: async () => {
// Refresh App.models to ensure personal provider models are current.
// Without this, newly added providers may not appear in the dropdown.
await fetchModels();
return App.models || [];
},
fetchRoles: async () => {
@@ -591,6 +631,11 @@ function _initSettingsListeners() {
document.getElementById('adminBannerBgHex')?.addEventListener('input', (e) => { if (/^#[0-9a-f]{6}$/i.test(e.target.value)) { document.getElementById('adminBannerBg').value = e.target.value; UI.updateBannerPreview(); }});
document.getElementById('adminBannerFgHex')?.addEventListener('input', (e) => { if (/^#[0-9a-f]{6}$/i.test(e.target.value)) { document.getElementById('adminBannerFg').value = e.target.value; UI.updateBannerPreview(); }});
// Admin — auto-compaction controls
document.getElementById('adminCompactionEnabled')?.addEventListener('change', (e) => {
document.getElementById('compactionConfigFields').style.display = e.target.checked ? '' : 'none';
});
// Admin — search provider controls
document.getElementById('adminSearchProvider')?.addEventListener('change', (e) => {
document.getElementById('searxngConfigFields').style.display = e.target.value === 'searxng' ? '' : 'none';

View File

@@ -103,6 +103,16 @@ function updateContextWarning() {
summarizeBtn.style.display = showSummarize ? 'inline-block' : 'none';
}
// Show auto-compact toggle next to summarize button
const autoToggle = document.getElementById('autoCompactToggle');
const autoCheck = document.getElementById('autoCompactCheck');
if (autoToggle && autoCheck) {
autoToggle.style.display = showSummarize ? 'inline-flex' : 'none';
// Reflect current channel setting
const chatSettings = chat?.settings || {};
autoCheck.checked = chatSettings.auto_compaction !== false; // default: on
}
if (pct >= 0.9 && !Tokens._warningDismissed) {
warning.style.display = 'flex';
warning.className = 'context-warning danger';

View File

@@ -803,6 +803,19 @@ Object.assign(UI, {
document.getElementById('searxngConfigFields').style.display =
(searchCfg.provider === 'searxng') ? '' : 'none';
// Auto-Compaction (global_settings)
const compactionCfg = getSetting('auto_compaction', {}) || {};
const compactionEnabled = document.getElementById('adminCompactionEnabled');
if (compactionEnabled) {
compactionEnabled.checked = !!compactionCfg.enabled;
document.getElementById('compactionConfigFields').style.display =
compactionCfg.enabled ? '' : 'none';
}
const compThreshold = document.getElementById('adminCompactionThreshold');
if (compThreshold) compThreshold.value = String(compactionCfg.threshold || 70);
const compCooldown = document.getElementById('adminCompactionCooldown');
if (compCooldown) compCooldown.value = String(compactionCfg.cooldown || 30);
if (vaultEl) {
try {
const vault = await API.adminGetVaultStatus();

View File

@@ -361,10 +361,11 @@ const UI = {
const meta = msg.metadata || {};
const count = meta.summarized_count || '?';
const model = meta.utility_model || 'utility model';
const trigger = meta.trigger === 'auto' ? ' · auto' : '';
return `
<div class="message-summary" data-msg-id="${esc(msg.id || '')}">
<div class="summary-header">
<span>📝 Conversation summary (${count} messages, by ${esc(model)})</span>
<span>📝 Conversation summary (${count} messages, by ${esc(model)}${trigger})</span>
</div>
<div class="msg-text">${formatMessage(msg.content)}</div>
</div>`;