Changeset 0.22.9 (#151)
This commit is contained in:
@@ -117,6 +117,11 @@ async function startApp() {
|
||||
}
|
||||
} catch (_) {}
|
||||
|
||||
// If no chat was restored, show the welcome/empty state
|
||||
if (!App.currentChatId) {
|
||||
UI.showEmptyState();
|
||||
}
|
||||
|
||||
UI.updateUser();
|
||||
UI.showAdminButton(API.isAdmin);
|
||||
initListeners();
|
||||
@@ -125,6 +130,12 @@ async function startApp() {
|
||||
try {
|
||||
Events.connect((window.__BASE__ || '') + '/ws');
|
||||
|
||||
// Once WS connects, re-fetch tools so browser extension tools appear
|
||||
// (ToolsToggle.init runs before WS is up → server omits browser tools)
|
||||
Events.on('ws.connected', () => {
|
||||
if (typeof ToolsToggle !== 'undefined') ToolsToggle.refresh();
|
||||
});
|
||||
|
||||
// Admin-only: persistent fallback alert banner (v0.17.0)
|
||||
Events.on('role.fallback', (payload) => {
|
||||
if (App.user?.role !== 'admin') return;
|
||||
@@ -161,80 +172,6 @@ function showFallbackBanner(data) {
|
||||
|
||||
// ── Modal Helpers ───────────────────────────
|
||||
|
||||
function openModal(id) {
|
||||
const el = document.getElementById(id);
|
||||
if (!el) return;
|
||||
el.classList.add('active');
|
||||
// Defer overflow check — browser needs a frame to lay out the modal
|
||||
requestAnimationFrame(() => {
|
||||
el.querySelectorAll('.modal-tabs').forEach(checkTabsOverflow);
|
||||
});
|
||||
}
|
||||
|
||||
function closeModal(id) {
|
||||
const el = document.getElementById(id);
|
||||
if (el) el.classList.remove('active');
|
||||
}
|
||||
|
||||
// Toggle .is-scrollable on tab bars that overflow horizontally
|
||||
// and inject arrow navigation buttons when needed.
|
||||
function checkTabsOverflow(tabs) {
|
||||
if (!tabs) return;
|
||||
const scrollable = tabs.scrollWidth > tabs.clientWidth + 1;
|
||||
|
||||
if (!scrollable) {
|
||||
// Remove wrapper if overflow went away (e.g. zoom decreased)
|
||||
const wrap = tabs.parentElement;
|
||||
if (wrap && wrap.classList.contains('modal-tabs-wrap')) {
|
||||
wrap.parentElement.insertBefore(tabs, wrap);
|
||||
wrap.remove();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Wrap if not already wrapped
|
||||
if (!tabs.parentElement.classList.contains('modal-tabs-wrap')) {
|
||||
const wrap = document.createElement('div');
|
||||
wrap.className = 'modal-tabs-wrap';
|
||||
tabs.parentElement.insertBefore(wrap, tabs);
|
||||
wrap.appendChild(tabs);
|
||||
|
||||
// Inject arrows
|
||||
const left = document.createElement('button');
|
||||
left.className = 'tab-arrow tab-arrow-left';
|
||||
left.innerHTML = '‹';
|
||||
left.addEventListener('click', () => { tabs.scrollLeft -= 120; });
|
||||
|
||||
const right = document.createElement('button');
|
||||
right.className = 'tab-arrow tab-arrow-right';
|
||||
right.innerHTML = '›';
|
||||
right.addEventListener('click', () => { tabs.scrollLeft += 120; });
|
||||
|
||||
wrap.appendChild(left);
|
||||
wrap.appendChild(right);
|
||||
|
||||
// Update arrow visibility on scroll
|
||||
tabs.addEventListener('scroll', () => updateTabArrows(tabs), { passive: true });
|
||||
}
|
||||
|
||||
updateTabArrows(tabs);
|
||||
}
|
||||
|
||||
function updateTabArrows(tabs) {
|
||||
const wrap = tabs.parentElement;
|
||||
if (!wrap || !wrap.classList.contains('modal-tabs-wrap')) return;
|
||||
|
||||
const left = wrap.querySelector('.tab-arrow-left');
|
||||
const right = wrap.querySelector('.tab-arrow-right');
|
||||
if (!left || !right) return;
|
||||
|
||||
const atStart = tabs.scrollLeft <= 2;
|
||||
const atEnd = tabs.scrollLeft + tabs.clientWidth >= tabs.scrollWidth - 2;
|
||||
|
||||
left.classList.toggle('visible', !atStart);
|
||||
right.classList.toggle('visible', !atEnd);
|
||||
}
|
||||
|
||||
// ── Auth Flow ────────────────────────────────
|
||||
|
||||
function showSplash(health) {
|
||||
|
||||
@@ -14,6 +14,9 @@ const ToolsToggle = (() => {
|
||||
knowledge: { label: 'Knowledge Bases', icon: '📚' },
|
||||
notes: { label: 'Notes', icon: '📝' },
|
||||
utilities: { label: 'Utilities', icon: '🧮' },
|
||||
workspace: { label: 'Workspace', icon: '🗂️' },
|
||||
context: { label: 'Context', icon: '📎' },
|
||||
memory: { label: 'Memory', icon: '🧠' },
|
||||
browser: { label: 'Extensions', icon: '🧩' },
|
||||
};
|
||||
|
||||
@@ -157,14 +160,15 @@ const ToolsToggle = (() => {
|
||||
const state = _categoryState(cat);
|
||||
const tools = _categories[cat];
|
||||
const expanded = _expanded.has(cat);
|
||||
const stateClass = state === 'all' ? 'enabled' : state === 'partial' ? 'partial' : '';
|
||||
const switchClass = state === 'all' ? 'on' : state === 'partial' ? 'on partial' : '';
|
||||
const chevron = tools.length > 1
|
||||
? `<span class="tools-popup-expand ${expanded ? 'open' : ''}" data-expand="${cat}">›</span>`
|
||||
: '';
|
||||
const labelExpand = tools.length > 1 ? ` data-expand="${cat}"` : '';
|
||||
|
||||
html += `<div class="tools-popup-category ${stateClass}" data-category="${cat}">
|
||||
<span class="tools-popup-check"></span>
|
||||
<span class="tools-popup-label">${meta.icon} ${meta.label}</span>
|
||||
html += `<div class="tools-popup-category" data-category="${cat}">
|
||||
<span class="tools-popup-switch ${switchClass}" data-switch="${cat}"><span class="tools-popup-switch-knob"></span></span>
|
||||
<span class="tools-popup-label"${labelExpand}>${meta.icon} ${meta.label}</span>
|
||||
${chevron}
|
||||
</div>`;
|
||||
|
||||
@@ -172,8 +176,8 @@ const ToolsToggle = (() => {
|
||||
if (expanded && tools.length > 1) {
|
||||
for (const t of tools) {
|
||||
const on = !_disabled.has(t.name);
|
||||
html += `<div class="tools-popup-tool ${on ? 'enabled' : ''}" data-tool="${t.name}">
|
||||
<span class="tools-popup-check"></span>
|
||||
html += `<div class="tools-popup-tool" data-tool="${t.name}">
|
||||
<span class="tools-popup-switch ${on ? 'on' : ''}" data-tool-switch="${t.name}"><span class="tools-popup-switch-knob"></span></span>
|
||||
<span class="tools-popup-label">${t.displayName}</span>
|
||||
</div>`;
|
||||
}
|
||||
@@ -196,23 +200,40 @@ const ToolsToggle = (() => {
|
||||
popup.addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
|
||||
// Expand/collapse chevron
|
||||
const expander = e.target.closest('.tools-popup-expand');
|
||||
// Toggle switch on category row
|
||||
const catSwitch = e.target.closest('[data-switch]');
|
||||
if (catSwitch) {
|
||||
_toggleCategory(catSwitch.dataset.switch);
|
||||
return;
|
||||
}
|
||||
|
||||
// Toggle switch on individual tool row
|
||||
const toolSwitch = e.target.closest('[data-tool-switch]');
|
||||
if (toolSwitch) {
|
||||
_toggleTool(toolSwitch.dataset.toolSwitch);
|
||||
return;
|
||||
}
|
||||
|
||||
// Expand/collapse: chevron or label click on multi-tool categories
|
||||
const expander = e.target.closest('[data-expand]');
|
||||
if (expander) {
|
||||
_toggleExpand(expander.dataset.expand);
|
||||
return;
|
||||
}
|
||||
|
||||
// Individual tool toggle
|
||||
// Individual tool label (no expand, just toggle)
|
||||
const toolRow = e.target.closest('.tools-popup-tool');
|
||||
if (toolRow) {
|
||||
_toggleTool(toolRow.dataset.tool);
|
||||
return;
|
||||
}
|
||||
|
||||
// Category toggle
|
||||
// Single-tool category label click (no chevron) — toggle
|
||||
const catRow = e.target.closest('.tools-popup-category');
|
||||
if (catRow) _toggleCategory(catRow.dataset.category);
|
||||
if (catRow) {
|
||||
_toggleCategory(catRow.dataset.category);
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
// Close on outside click
|
||||
|
||||
@@ -539,9 +539,10 @@ const UI = {
|
||||
},
|
||||
|
||||
showEmptyState() {
|
||||
var base = window.__BASE__ || '';
|
||||
document.getElementById('chatMessages').innerHTML = `
|
||||
<div class="empty-state">
|
||||
<div class="empty-logo"><img src="favicon-256.png" class="empty-logo-img" alt=""></div>
|
||||
<div class="empty-logo"><img src="${base}/favicon-256.png" class="empty-logo-img" alt=""></div>
|
||||
<h2>Chat Switchboard</h2>
|
||||
<p>Select a model and start chatting</p>
|
||||
</div>`;
|
||||
@@ -995,6 +996,8 @@ const UI = {
|
||||
|
||||
showAdminButton(show) {
|
||||
document.getElementById('menuAdmin').style.display = show ? '' : 'none';
|
||||
var dbg = document.getElementById('menuDebug');
|
||||
if (dbg) dbg.style.display = show ? '' : 'none';
|
||||
},
|
||||
|
||||
// ── Generating State ─────────────────────
|
||||
|
||||
@@ -348,13 +348,18 @@ function popOutExtBlock(blockId) {
|
||||
|
||||
const lang = block.getAttribute('data-ext-lang') || '';
|
||||
|
||||
// Read current theme colors so the iframe respects light/dark mode
|
||||
const cs = getComputedStyle(document.documentElement);
|
||||
const bgColor = cs.getPropertyValue('--bg-surface').trim() || '#fff';
|
||||
const textColor = cs.getPropertyValue('--text').trim() || '#1a1a2e';
|
||||
|
||||
// Wrap in a minimal HTML document for the iframe
|
||||
const html = `<!DOCTYPE html>
|
||||
<html><head>
|
||||
<meta charset="utf-8">
|
||||
<style>
|
||||
body { margin: 0; padding: 16px; display: flex; justify-content: center;
|
||||
align-items: flex-start; min-height: 100vh; background: #fff;
|
||||
align-items: flex-start; min-height: 100vh; background: ${bgColor}; color: ${textColor};
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; }
|
||||
body > * { max-width: 100%; }
|
||||
svg { max-width: 100%; height: auto; }
|
||||
|
||||
@@ -791,3 +791,77 @@ function showConfirm(message, opts = {}) {
|
||||
overlay.querySelector('[data-action="cancel"]').focus();
|
||||
});
|
||||
}
|
||||
|
||||
// ── Modal open/close ────────────────────────
|
||||
// Generic modal show/hide used by settings, admin, debug, team-admin modals.
|
||||
// Moved here from app.js so all surfaces (not just chat) can use modals.
|
||||
|
||||
function openModal(id) {
|
||||
const el = document.getElementById(id);
|
||||
if (!el) return;
|
||||
el.classList.add('active');
|
||||
// Defer overflow check — browser needs a frame to lay out the modal
|
||||
requestAnimationFrame(() => {
|
||||
el.querySelectorAll('.modal-tabs').forEach(checkTabsOverflow);
|
||||
});
|
||||
}
|
||||
|
||||
function closeModal(id) {
|
||||
const el = document.getElementById(id);
|
||||
if (el) el.classList.remove('active');
|
||||
}
|
||||
|
||||
// Toggle .is-scrollable on tab bars that overflow horizontally
|
||||
// and inject arrow navigation buttons when needed.
|
||||
function checkTabsOverflow(tabs) {
|
||||
if (!tabs) return;
|
||||
const scrollable = tabs.scrollWidth > tabs.clientWidth + 1;
|
||||
|
||||
if (!scrollable) {
|
||||
const wrap = tabs.parentElement;
|
||||
if (wrap && wrap.classList.contains('modal-tabs-wrap')) {
|
||||
wrap.parentElement.insertBefore(tabs, wrap);
|
||||
wrap.remove();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (!tabs.parentElement.classList.contains('modal-tabs-wrap')) {
|
||||
const wrap = document.createElement('div');
|
||||
wrap.className = 'modal-tabs-wrap';
|
||||
tabs.parentElement.insertBefore(wrap, tabs);
|
||||
wrap.appendChild(tabs);
|
||||
|
||||
const left = document.createElement('button');
|
||||
left.className = 'tab-arrow tab-arrow-left';
|
||||
left.innerHTML = '‹';
|
||||
left.addEventListener('click', () => { tabs.scrollLeft -= 120; });
|
||||
|
||||
const right = document.createElement('button');
|
||||
right.className = 'tab-arrow tab-arrow-right';
|
||||
right.innerHTML = '›';
|
||||
right.addEventListener('click', () => { tabs.scrollLeft += 120; });
|
||||
|
||||
wrap.appendChild(left);
|
||||
wrap.appendChild(right);
|
||||
|
||||
tabs.addEventListener('scroll', () => updateTabArrows(tabs), { passive: true });
|
||||
}
|
||||
|
||||
updateTabArrows(tabs);
|
||||
}
|
||||
|
||||
function updateTabArrows(tabs) {
|
||||
const wrap = tabs.parentElement;
|
||||
if (!wrap || !wrap.classList.contains('modal-tabs-wrap')) return;
|
||||
|
||||
const left = wrap.querySelector('.tab-arrow-left');
|
||||
const right = wrap.querySelector('.tab-arrow-right');
|
||||
if (!left || !right) return;
|
||||
|
||||
const atStart = tabs.scrollLeft <= 2;
|
||||
const atEnd = tabs.scrollLeft + tabs.clientWidth >= tabs.scrollWidth - 2;
|
||||
|
||||
left.classList.toggle('visible', !atStart);
|
||||
right.classList.toggle('visible', !atEnd);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user