mirror of
https://github.com/pewdiepie-archdaemon/odysseus.git
synced 2026-10-14 10:52:21 +02:00
Squash Odysseus development history
This commit is contained in:
+214
-124
@@ -3,16 +3,16 @@
|
||||
// ES6 module — entry point, no exports (wires all modules together)
|
||||
// ============================================
|
||||
import Storage from './js/storage.js';
|
||||
import uiModule from './js/ui.js';
|
||||
import uiModule from './js/ui.js?v=20260908weekhoverfix1';
|
||||
import workspaceModule from './js/workspace.js';
|
||||
import fileHandlerModule from './js/fileHandler.js';
|
||||
import modelsModule from './js/models.js?v=20260715startupcalm2';
|
||||
import fileHandlerModule from './js/fileHandler.js?v=20260909mobileattachmentedit1';
|
||||
import modelsModule from './js/models.js';
|
||||
import ragModule from './js/rag.js';
|
||||
import presetsModule from './js/presets.js';
|
||||
import presetsModule from './js/presets.js?v=20260908personaname1';
|
||||
import searchModule from './js/search.js';
|
||||
import chatModule from './js/chat.js?v=20260819approvalcontrol1';
|
||||
import compareModule from './js/compare/index.js?v=20260819approvalcontrol1';
|
||||
import documentModule from './js/document.js?v=20260815approvalsave1';
|
||||
import chatModule from './js/chat.js?v=20260910shelltoggle1';
|
||||
import compareModule from './js/compare/index.js?v=20260909mobilepaneaddscroll1';
|
||||
import documentModule from './js/document.js?v=20260910minimizedcontext1';
|
||||
import searchChatModule from './js/search-chat.js';
|
||||
import { makeWindowDraggable } from './js/windowDrag.js';
|
||||
import {
|
||||
@@ -22,23 +22,30 @@ import {
|
||||
settleSessionHydration
|
||||
} from './js/startupShell.js';
|
||||
import markdownModule from './js/markdown.js';
|
||||
import chatRenderer from './js/chatRenderer.js?v=20260819approvalcontrol1';
|
||||
import chatRenderer from './js/chatRenderer.js?v=20260910streamlinks2';
|
||||
// Keep this specifier identical to every consumer (especially chat.js).
|
||||
// Different query strings create separate ES-module instances with separate
|
||||
// current-session state, so the picker can display one model while chat sends
|
||||
// through another session.
|
||||
import sessionModule from './js/sessions.js';
|
||||
import memoryModule from './js/memory.js?v=20260722memoryloading1';
|
||||
import { startBackgroundToolJobs } from './js/backgroundToolJobs.js';
|
||||
import memoryModule from './js/memory.js';
|
||||
import voiceRecorderModule from './js/voiceRecorder.js';
|
||||
import censorModule from './js/censor.js';
|
||||
import galleryModule from './js/gallery.js';
|
||||
import { UI_VIS_DEFAULT_OFF, resolveVisibility } from './js/ui_visibility.js';
|
||||
import tasksModule from './js/tasks.js?v=20260723tasksbulkfeedback1';
|
||||
import calendarModule from './js/calendar.js';
|
||||
import notesModule from './js/notes.js';
|
||||
import adminModule from './js/admin.js?v=20260716openrouter3';
|
||||
import settingsModule from './js/settings.js?v=20260815approvalsave1';
|
||||
import galleryModule from './js/gallery.js?v=20260910promptcopy1';
|
||||
import { UI_VIS_DEFAULT_OFF, resolveVisibility } from './js/ui_visibility.js?v=20260829chatstyle12';
|
||||
import tasksModule from './js/tasks.js?v=20260910tasksortpicker6';
|
||||
import calendarModule from './js/calendar.js?v=20260903weekscrollstable1';
|
||||
import notesModule from './js/notes.js?v=20260911notesselectioncancel1';
|
||||
import adminModule from './js/admin.js?v=20260908notificationcopy1';
|
||||
import settingsModule from './js/settings.js?v=20260909defaultmodelfix1';
|
||||
// Eagerly bind unified minimize/restore behavior across all tool modals.
|
||||
import './js/modalManager.js?v=20260723compareicon2';
|
||||
import './js/modalManager.js';
|
||||
import './js/chipScroll.js?v=20260903calendarchips1';
|
||||
import './js/mobileBulkSelect.js?v=20260910selecthold1';
|
||||
// Desktop window tiling — drag a modal near an edge/corner to snap.
|
||||
import './js/tileManager.js';
|
||||
import themeModule from './js/theme.js';
|
||||
import './js/tileManager.js?v=20260910responsivebounds1';
|
||||
import themeModule from './js/theme.js?v=20260909effectspeed1';
|
||||
// IMPORTANT: import cookbook.js with NO ?v= query — the same plain specifier
|
||||
// every other importer (cookbook-hwfit.js / cookbook-diagnosis.js) uses. A query
|
||||
// mismatch makes the browser load cookbook.js twice as separate modules (two
|
||||
@@ -46,12 +53,12 @@ import themeModule from './js/theme.js';
|
||||
// unversioned so this can't recur.
|
||||
import cookbookModule from './js/cookbook.js';
|
||||
import groupModule from './js/group.js';
|
||||
import * as researchPanelModule from './js/research/panel.js?v=20260630researchthumb';
|
||||
import * as researchPanelModule from './js/research/panel.js?v=20260910researchdeeplink1';
|
||||
import ttsModule from './js/tts-ai.js';
|
||||
import spinnerModule from './js/spinner.js';
|
||||
import { initKeyboardShortcuts } from './js/keyboard-shortcuts.js';
|
||||
import { initKeyboardShortcuts } from './js/keyboard-shortcuts.js?v=20260829chatstyle12';
|
||||
import { getSettings } from './js/appConfig.js';
|
||||
import { initSidebarLayout, syncRailSide } from './js/sidebar-layout.js?v=20260715startupclean';
|
||||
import { initSidebarLayout, syncRailSide } from './js/sidebar-layout.js?v=20260910sidebarbounce1';
|
||||
import { initSectionCollapse, initSectionDrag } from './js/section-management.js';
|
||||
|
||||
const API_BASE = window.location.origin;
|
||||
@@ -176,8 +183,9 @@ function initRailHoverLabels() {
|
||||
'rail-email': 'Email',
|
||||
'rail-gallery': 'Gallery',
|
||||
'rail-archive': 'Library',
|
||||
'rail-memory': 'Brain',
|
||||
'rail-memory': 'Memory',
|
||||
'rail-notes': 'Notes',
|
||||
'rail-skills': 'Skills',
|
||||
'rail-tasks': 'Tasks',
|
||||
'rail-theme': 'Theme',
|
||||
'rail-settings': 'Settings',
|
||||
@@ -237,12 +245,12 @@ async function _refreshDefaultChat() {
|
||||
// synchronously; later reads should call _refreshDefaultChat() first.
|
||||
_refreshDefaultChat();
|
||||
|
||||
async function _createDirectChatFromPreferredModel() {
|
||||
async function _createDirectChatFromPreferredModel(opts = {}) {
|
||||
if (!sessionModule) return false;
|
||||
|
||||
const pending = sessionModule.getPendingChat && sessionModule.getPendingChat();
|
||||
if (pending && pending.url && pending.modelId && pending.endpointId) {
|
||||
sessionModule.createDirectChat(pending.url, pending.modelId, pending.endpointId, { source: pending.source || 'manual' });
|
||||
sessionModule.createDirectChat(pending.url, pending.modelId, pending.endpointId, { source: pending.source || 'manual', keepDocument: !!opts.keepDocument });
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -250,20 +258,20 @@ async function _createDirectChatFromPreferredModel() {
|
||||
const currentId = sessionModule.getCurrentSessionId();
|
||||
const current = sessions.find(s => s.id === currentId);
|
||||
if (current && current.endpoint_url && current.model && current.endpoint_id) {
|
||||
sessionModule.createDirectChat(current.endpoint_url, current.model, current.endpoint_id);
|
||||
sessionModule.createDirectChat(current.endpoint_url, current.model, current.endpoint_id, { keepDocument: !!opts.keepDocument });
|
||||
return true;
|
||||
}
|
||||
|
||||
const dc = await _refreshDefaultChat();
|
||||
if (dc) {
|
||||
sessionModule.createDirectChat(dc.endpoint_url, dc.model, dc.endpoint_id, { source: 'default' });
|
||||
sessionModule.createDirectChat(dc.endpoint_url, dc.model, dc.endpoint_id, { source: 'default', keepDocument: !!opts.keepDocument });
|
||||
return true;
|
||||
}
|
||||
|
||||
const withModel = sessions.filter(s => s.endpoint_url && s.model);
|
||||
if (withModel.length > 0) {
|
||||
const last = withModel[0]; // sessions are sorted by recent
|
||||
sessionModule.createDirectChat(last.endpoint_url, last.model, last.endpoint_id);
|
||||
sessionModule.createDirectChat(last.endpoint_url, last.model, last.endpoint_id, { keepDocument: !!opts.keepDocument });
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -355,7 +363,8 @@ function initializeEventListeners() {
|
||||
const link = e.target.closest('a.chat-link');
|
||||
if (!link) return;
|
||||
const href = link.getAttribute('href');
|
||||
if (href && href.startsWith('#') && sessionModule) {
|
||||
const isListExpander = /^#(?:notes|skills|memories|events|sessions)-more-/.test(href || '');
|
||||
if (href && href.startsWith('#') && !isListExpander && sessionModule) {
|
||||
e.preventDefault();
|
||||
sessionModule.selectSession(href.slice(1));
|
||||
}
|
||||
@@ -392,19 +401,54 @@ function initializeEventListeners() {
|
||||
|
||||
const exportMenu = el('export-dropdown-menu');
|
||||
if (exportDlBtn && exportMenu) {
|
||||
exportDlBtn.addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
// Keep Settings available from the chat menu even before a new chat has
|
||||
// received its first prompt (when the header has no session actions yet).
|
||||
if (!el('export-settings-btn')) {
|
||||
const settingsItem = document.createElement('div');
|
||||
settingsItem.className = 'export-dropdown-item';
|
||||
settingsItem.id = 'export-settings-btn';
|
||||
settingsItem.classList.add('export-settings-text-only');
|
||||
settingsItem.innerHTML = '<span class="dropdown-icon"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="3"></circle><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 1 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 1 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06A2 2 0 1 1 7.04 4.3l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 1 1 4 0v.09A1.65 1.65 0 0 0 15 4.6a1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 1 1 0 4h-.09A1.65 1.65 0 0 0 19.4 15z"></path></svg></span><span>Settings</span>';
|
||||
const deleteItem = exportMenu.querySelector('#export-delete-btn');
|
||||
if (deleteItem) exportMenu.insertBefore(settingsItem, deleteItem);
|
||||
else exportMenu.appendChild(settingsItem);
|
||||
settingsItem.addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
exportMenu.classList.remove('open');
|
||||
if (window.chatModule?.openContextSettings && window.chatModule.openContextSettings()) return;
|
||||
if (typeof settingsModule !== 'undefined' && settingsModule?.open) settingsModule.open();
|
||||
else if (typeof adminModule !== 'undefined' && adminModule?.open) adminModule.open();
|
||||
else if (window.settingsModule?.open) window.settingsModule.open();
|
||||
else if (window.adminModule?.open) window.adminModule.open();
|
||||
});
|
||||
}
|
||||
const openExportMenuFrom = (anchorEl, { align = 'right' } = {}) => {
|
||||
if (exportMenu.classList.contains('open')) {
|
||||
exportMenu.classList.remove('open');
|
||||
} else {
|
||||
// Move menu to body so it's not affected by ancestor transforms
|
||||
if (exportMenu.parentElement !== document.body) document.body.appendChild(exportMenu);
|
||||
const rect = exportDlBtn.getBoundingClientRect();
|
||||
const rect = (anchorEl || exportDlBtn).getBoundingClientRect();
|
||||
exportMenu.style.top = (rect.bottom + 4) + 'px';
|
||||
exportMenu.style.left = 'auto';
|
||||
exportMenu.style.right = (window.innerWidth - rect.right) + 'px';
|
||||
if (align === 'left') {
|
||||
exportMenu.style.left = Math.max(8, rect.left) + 'px';
|
||||
exportMenu.style.right = 'auto';
|
||||
} else {
|
||||
exportMenu.style.left = 'auto';
|
||||
exportMenu.style.right = (window.innerWidth - rect.right) + 'px';
|
||||
}
|
||||
exportMenu.classList.add('open');
|
||||
if (align === 'left') {
|
||||
const menuRect = exportMenu.getBoundingClientRect();
|
||||
if (menuRect.right > window.innerWidth - 8) {
|
||||
exportMenu.style.left = `${Math.max(8, window.innerWidth - menuRect.width - 8)}px`;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
exportDlBtn.addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
openExportMenuFrom(exportDlBtn);
|
||||
});
|
||||
document.addEventListener('click', () => exportMenu.classList.remove('open'));
|
||||
document.addEventListener('keydown', (e) => {
|
||||
@@ -429,8 +473,17 @@ function initializeEventListeners() {
|
||||
if (currentMeta) {
|
||||
currentMeta.style.cursor = 'pointer';
|
||||
currentMeta.addEventListener('click', (e) => {
|
||||
const incognitoChk = el('incognito-toggle');
|
||||
const mobile = window.matchMedia && window.matchMedia('(max-width: 768px)').matches;
|
||||
if (mobile && incognitoChk && incognitoChk.checked) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
const incognitoBtnForTitle = el('incognito-btn');
|
||||
if (incognitoBtnForTitle) incognitoBtnForTitle.click();
|
||||
return;
|
||||
}
|
||||
e.stopPropagation();
|
||||
exportDlBtn.click();
|
||||
openExportMenuFrom(currentMeta, { align: 'left' });
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -496,20 +549,6 @@ function initializeEventListeners() {
|
||||
});
|
||||
}
|
||||
|
||||
// Export menu: Compact current chat context
|
||||
const exportCompactBtn = el('export-compact-btn');
|
||||
if (exportCompactBtn) {
|
||||
exportCompactBtn.addEventListener('click', async (e) => {
|
||||
e.stopPropagation();
|
||||
exportMenu.classList.remove('open');
|
||||
if (window.compactCurrentChatContext) {
|
||||
await window.compactCurrentChatContext();
|
||||
} else {
|
||||
uiModule.showError('Compact action is not ready yet');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Export: PDF
|
||||
const exportPdfBtn = el('export-pdf-btn');
|
||||
if (exportPdfBtn) {
|
||||
@@ -535,33 +574,6 @@ function initializeEventListeners() {
|
||||
});
|
||||
}
|
||||
|
||||
// Export: Save to Docs
|
||||
const exportDocBtn = el('export-doc-btn');
|
||||
if (exportDocBtn) {
|
||||
exportDocBtn.addEventListener('click', async (e) => {
|
||||
e.stopPropagation();
|
||||
exportMenu.classList.remove('open');
|
||||
try {
|
||||
const sessionId = sessionModule.getCurrentSessionId();
|
||||
const texts = _serializeChatTranscript();
|
||||
const meta = sessionModule.getSessions().find(s => s.id === sessionId);
|
||||
const title = meta?.name || 'Untitled';
|
||||
const res = await fetch(`${API_BASE}/api/document`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ session_id: sessionId, title, content: texts }),
|
||||
});
|
||||
if (!res.ok) throw new Error('Failed');
|
||||
const doc = await res.json();
|
||||
if (documentModule) documentModule.loadDocument(doc.id);
|
||||
uiModule.showToast('Saved to documents');
|
||||
} catch (err) {
|
||||
console.error('Save to docs failed:', err);
|
||||
uiModule.showError('Failed to save to documents');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Export menu: Delete current chat
|
||||
const exportDeleteBtn = el('export-delete-btn');
|
||||
if (exportDeleteBtn) {
|
||||
@@ -1094,6 +1106,14 @@ function initializeEventListeners() {
|
||||
}
|
||||
});
|
||||
}
|
||||
const notesNewNoteBtn = el('notes-new-note-btn');
|
||||
if (notesNewNoteBtn) {
|
||||
notesNewNoteBtn.addEventListener('click', (event) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
notesModule?.newNote?.();
|
||||
});
|
||||
}
|
||||
// Refresh notes due-reminder badge on load and every 5 minutes
|
||||
if (notesModule && notesModule.refreshDueBadge) {
|
||||
notesModule.refreshDueBadge();
|
||||
@@ -1220,6 +1240,7 @@ function initializeEventListeners() {
|
||||
setTimeout(_goFullscreen, 200);
|
||||
},
|
||||
'/memory': () => document.getElementById('tool-memory-btn')?.click(),
|
||||
'/skills': () => document.getElementById('tool-skills-btn')?.click(),
|
||||
'/gallery': () => document.getElementById('tool-gallery-btn')?.click(),
|
||||
'/tasks': () => document.getElementById('tool-tasks-btn')?.click(),
|
||||
'/library': () => sessionModule && sessionModule.openLibrary && sessionModule.openLibrary(),
|
||||
@@ -1675,9 +1696,24 @@ function initializeEventListeners() {
|
||||
const toolMemoryBtn = el('tool-memory-btn');
|
||||
if (toolMemoryBtn && memoryModal) {
|
||||
toolMemoryBtn.addEventListener('click', () => {
|
||||
memoryModal.classList.remove('hidden');
|
||||
if (memoryModule && memoryModule.renderMemoryList) memoryModule.renderMemoryList();
|
||||
if (memoryModule && memoryModule.updateMemoryCount) memoryModule.updateMemoryCount();
|
||||
if (memoryModule && memoryModule.openMemoryModal) memoryModule.openMemoryModal('browse');
|
||||
else {
|
||||
memoryModal.classList.remove('hidden');
|
||||
if (memoryModule && memoryModule.renderMemoryList) memoryModule.renderMemoryList();
|
||||
if (memoryModule && memoryModule.updateMemoryCount) memoryModule.updateMemoryCount();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Sidebar Skills button
|
||||
const toolSkillsBtn = el('tool-skills-btn');
|
||||
if (toolSkillsBtn && memoryModal) {
|
||||
toolSkillsBtn.addEventListener('click', () => {
|
||||
if (memoryModule && memoryModule.openMemoryModal) memoryModule.openMemoryModal('skills');
|
||||
else {
|
||||
memoryModal.classList.remove('hidden');
|
||||
document.querySelector('.memory-tab[data-memory-tab="skills"]')?.click();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1836,8 +1872,10 @@ function initializeEventListeners() {
|
||||
if (toggle) toggle.classList.toggle('mode-chat', mode === 'chat');
|
||||
// Workspace pill + overflow entry are agent-only - hide immediately (no flash).
|
||||
try { workspaceModule.applyMode(mode); } catch (_) {}
|
||||
// Delay tool glow-up for a staggered effect
|
||||
setTimeout(() => applyModeToToggles(mode), 500);
|
||||
// Tool state is request authority, so synchronize it immediately. A
|
||||
// delayed callback could overwrite a user's fast toggle click and send
|
||||
// a different allow_bash/allow_web_search value than the UI showed.
|
||||
applyModeToToggles(mode);
|
||||
}
|
||||
window.__odysseusSetChatMode = setMode;
|
||||
agentBtn.addEventListener('click', () => {
|
||||
@@ -2178,13 +2216,12 @@ function initializeEventListeners() {
|
||||
menu.classList.add('closing');
|
||||
plusBtn.classList.remove('expanded');
|
||||
if (pickerWrap) pickerWrap.style.visibility = '';
|
||||
// Item delays max at 0.18s + 0.20s anim = 0.38s for items, container
|
||||
// delay 0.16s + 0.22s = 0.38s. 400ms covers both with margin.
|
||||
// Keep closing nearly immediate so the tools button feels direct.
|
||||
setTimeout(() => {
|
||||
menu.classList.add('hidden');
|
||||
menu.classList.remove('closing');
|
||||
if (ownerWrap) ownerWrap.appendChild(menu); // restore from <body> portal
|
||||
}, 400);
|
||||
}, 100);
|
||||
}
|
||||
// Close menu when clicking any item inside it. preventDefault on pointerdown
|
||||
// so tapping an item (e.g. Attach files) doesn't steal focus from the message
|
||||
@@ -2568,9 +2605,15 @@ function initializeEventListeners() {
|
||||
// ── Group toggle button (chatbox indicator) — click to deactivate ──
|
||||
const groupToggleBtn = el('group-toggle-btn');
|
||||
if (groupToggleBtn) {
|
||||
groupToggleBtn.addEventListener('click', () => {
|
||||
_syncGroupIndicator(false);
|
||||
groupModule.stopGroup();
|
||||
groupToggleBtn.addEventListener('click', (e) => {
|
||||
if (e.target.closest('.tool-indicator-x')) {
|
||||
_syncGroupIndicator(false);
|
||||
groupModule.stopGroup();
|
||||
return;
|
||||
}
|
||||
if (presetsModule && presetsModule.openCustomPresetModal) {
|
||||
presetsModule.openCustomPresetModal('group');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -2592,14 +2635,16 @@ function initializeEventListeners() {
|
||||
if (incognitoBtn) {
|
||||
incognitoBtn.addEventListener('mousedown', (e) => e.preventDefault());
|
||||
incognitoBtn.addEventListener('click', () => {
|
||||
// Don't toggle mid-chat — incognito only changeable from welcome screen
|
||||
const ws = el('welcome-screen');
|
||||
if (ws && ws.classList.contains('hidden')) return;
|
||||
const chk = el('incognito-toggle');
|
||||
// Starting Nobody is only available from the welcome screen, but ending
|
||||
// it must work after the first message too. The active header/title
|
||||
// controls depend on this path once the welcome button is hidden.
|
||||
if (ws && ws.classList.contains('hidden') && !chk.checked) return;
|
||||
chk.checked = !chk.checked;
|
||||
incognitoBtn.classList.toggle('active', chk.checked);
|
||||
const tipEl = el('welcome-tip');
|
||||
incognitoBtn.title = chk.checked ? 'Disable Nobody mode' : 'Enable Nobody mode — no memory, no history saved';
|
||||
incognitoBtn.title = chk.checked ? 'Disable Nobody mode' : 'Enable Nobody mode — no memory or skill injection, no history saved';
|
||||
const welcomeName = document.querySelector('.welcome-name');
|
||||
if (chk.checked) {
|
||||
try {
|
||||
@@ -2623,7 +2668,13 @@ function initializeEventListeners() {
|
||||
welcomeSub.textContent = "Who am I? I'm nobody.";
|
||||
welcomeSub.style.display = '';
|
||||
}
|
||||
if (tipEl) { tipEl.dataset.originalTip = tipEl.textContent; tipEl.textContent = 'Temporary session \u2014 won\u2019t be saved and no memory activation.'; tipEl.style.opacity = '0.5'; tipEl.style.marginTop = '8px'; }
|
||||
if (tipEl) {
|
||||
tipEl.dataset.originalTip = tipEl.textContent;
|
||||
tipEl.classList.add('nobody-session-note');
|
||||
tipEl.innerHTML = '<span>No memory or skill activation.</span><span>Temporary session \u2014 won\u2019t be saved.</span>';
|
||||
tipEl.style.opacity = '0.5';
|
||||
tipEl.style.marginTop = '16px';
|
||||
}
|
||||
// Default to plain chat: disable tools visually, switch to chat mode.
|
||||
// IMPORTANT: don't overwrite the user's persisted per-mode tool prefs
|
||||
// (`web_agent`, `bash_agent`, `web_chat`, `bash_chat`). Nobody mode is
|
||||
@@ -2673,7 +2724,13 @@ function initializeEventListeners() {
|
||||
}
|
||||
welcomeSub2.style.display = '';
|
||||
}
|
||||
if (tipEl && tipEl.dataset.originalTip) { tipEl.textContent = tipEl.dataset.originalTip; tipEl.style.opacity = ''; tipEl.style.marginTop = ''; }
|
||||
if (tipEl && Object.prototype.hasOwnProperty.call(tipEl.dataset, 'originalTip')) {
|
||||
tipEl.textContent = tipEl.dataset.originalTip;
|
||||
delete tipEl.dataset.originalTip;
|
||||
tipEl.classList.remove('nobody-session-note');
|
||||
tipEl.style.opacity = '';
|
||||
tipEl.style.marginTop = '';
|
||||
}
|
||||
// Heal any previously-persisted false values from the old Nobody bug
|
||||
// so agent-mode defaults (web/bash ON) come back.
|
||||
const _ts = Storage.getJSON(Storage.KEYS.TOGGLES, {});
|
||||
@@ -3145,6 +3202,7 @@ function initializeEventListeners() {
|
||||
|
||||
function injectMinimizeButton(modal) {
|
||||
if (!modal || !modal.classList || !modal.classList.contains('modal')) return;
|
||||
if (modal.dataset.noMinimize === 'true') return;
|
||||
if (modal.id && SKIP_IDS.has(modal.id)) return;
|
||||
// Modals managed by the new modalManager (Modals.register) get their own
|
||||
// .modal-minimize-btn and chips via the .minimized-dock-chip system.
|
||||
@@ -3313,13 +3371,40 @@ function initializeEventListeners() {
|
||||
async function _handleNewChatAction({ preferModel = true, focus = true } = {}) {
|
||||
if (!sessionModule) return;
|
||||
if (_closeCompareIfActive()) return;
|
||||
const hasOpenDocument = !!(
|
||||
documentModule
|
||||
&& documentModule.isPanelOpen
|
||||
&& documentModule.isPanelOpen()
|
||||
&& documentModule.getCurrentDocId
|
||||
&& documentModule.getCurrentDocId()
|
||||
);
|
||||
const shouldCarryDocument = hasOpenDocument && !!(
|
||||
uiModule?.styledConfirm
|
||||
? await uiModule.styledConfirm('Bring open document to new chat?', {
|
||||
title: 'New chat',
|
||||
confirmText: 'OK',
|
||||
cancelText: 'No',
|
||||
})
|
||||
: window.confirm('Bring open document to new chat?')
|
||||
);
|
||||
_deactivateIncognito();
|
||||
// Clear character on new chat
|
||||
if (presetsModule && presetsModule.deactivateCharacter) presetsModule.deactivateCharacter();
|
||||
// Clear research mode if active
|
||||
const _resChk = el('research-toggle');
|
||||
if (_resChk && _resChk.checked) _syncResearchIndicator(false);
|
||||
if (preferModel && await _createDirectChatFromPreferredModel()) return;
|
||||
if (preferModel && await _createDirectChatFromPreferredModel({ keepDocument: shouldCarryDocument })) {
|
||||
if (shouldCarryDocument) {
|
||||
try {
|
||||
await sessionModule.materializePendingSession();
|
||||
await documentModule.moveActiveDocumentToCurrentChat?.({ quiet: true });
|
||||
} catch (e) {
|
||||
console.warn('Failed to carry document into new chat:', e);
|
||||
uiModule?.showError?.('Could not bring document into the new chat');
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
// No models at all — show welcome screen
|
||||
_startFreshChat();
|
||||
const docBtn3 = el('overflow-doc-btn');
|
||||
@@ -3346,17 +3431,8 @@ function initializeEventListeners() {
|
||||
// the default model attaches when they hit send.
|
||||
const mobileNewChat = el('mobile-new-chat-btn');
|
||||
if (mobileNewChat) {
|
||||
mobileNewChat.addEventListener('click', () => {
|
||||
if (!sessionModule) return;
|
||||
if (_closeCompareIfActive()) return;
|
||||
_deactivateIncognito();
|
||||
_startFreshChat();
|
||||
document.querySelectorAll('.session-item.active').forEach(s => s.classList.remove('active'));
|
||||
// Focus the composer synchronously so mobile keyboards pop open.
|
||||
// iOS Safari only honours programmatic focus inside the original click
|
||||
// callback — a setTimeout breaks the user-gesture chain.
|
||||
const _input = el('message-input');
|
||||
if (_input) { try { _input.focus(); } catch (_) {} }
|
||||
mobileNewChat.addEventListener('click', async () => {
|
||||
await _handleNewChatAction();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -3707,6 +3783,8 @@ function startOdysseusApp() {
|
||||
searchModule.init();
|
||||
chatModule.init(API_BASE);
|
||||
chatModule.initListeners();
|
||||
startBackgroundToolJobs({ getSessionId: () => sessionModule.getCurrentSessionId(),
|
||||
addMessage: (...args) => chatRenderer.addMessage(...args), base: API_BASE });
|
||||
groupModule.init(API_BASE);
|
||||
// Initialize compare module
|
||||
if (compareModule) {
|
||||
@@ -3737,15 +3815,16 @@ function startOdysseusApp() {
|
||||
|
||||
// Rail tool buttons — delegate to sidebar tool buttons
|
||||
const _railToolMap = {
|
||||
'rail-compare': 'tool-compare-btn',
|
||||
'rail-research': 'tool-research-btn',
|
||||
'rail-cookbook': 'tool-cookbook-btn',
|
||||
'rail-archive': 'tool-library-btn',
|
||||
'rail-gallery': 'tool-gallery-btn',
|
||||
'rail-tasks': 'tool-tasks-btn',
|
||||
'rail-calendar': 'tool-calendar-btn',
|
||||
'rail-notes': 'tool-notes-btn',
|
||||
'rail-compare': 'tool-compare-btn',
|
||||
'rail-cookbook': 'tool-cookbook-btn',
|
||||
'rail-research': 'tool-research-btn',
|
||||
'rail-gallery': 'tool-gallery-btn',
|
||||
'rail-archive': 'tool-library-btn',
|
||||
'rail-memory': 'tool-memory-btn',
|
||||
'rail-notes': 'tool-notes-btn',
|
||||
'rail-skills': 'tool-skills-btn',
|
||||
'rail-tasks': 'tool-tasks-btn',
|
||||
'rail-theme': 'tool-theme-btn',
|
||||
'rail-email': 'email-section-title',
|
||||
};
|
||||
@@ -4030,12 +4109,18 @@ function startOdysseusApp() {
|
||||
return;
|
||||
}
|
||||
|
||||
// New chat mode — empty input, no attachments, no STT
|
||||
if (!hasText && !hasFiles && sendBtn.dataset.mode === 'newchat') {
|
||||
if (sessionModule) {
|
||||
const sessions = sessionModule.getSessions();
|
||||
const currentId = sessionModule.getCurrentSessionId();
|
||||
const current = sessions.find(s => s.id === currentId);
|
||||
// New chat mode — empty input, no attachments, no STT
|
||||
if (!hasText && !hasFiles && sendBtn.dataset.mode === 'newchat') {
|
||||
const isAlreadyNewChat = document.getElementById('chat-container')?.classList.contains('welcome-active');
|
||||
if (isAlreadyNewChat) {
|
||||
if (messageInput) messageInput.focus();
|
||||
_updateSendBtnIcon();
|
||||
return;
|
||||
}
|
||||
if (sessionModule) {
|
||||
const sessions = sessionModule.getSessions();
|
||||
const currentId = sessionModule.getCurrentSessionId();
|
||||
const current = sessions.find(s => s.id === currentId);
|
||||
if (current && current.endpoint_url && current.model) {
|
||||
sessionModule.createDirectChat(current.endpoint_url, current.model, current.endpoint_id);
|
||||
} else {
|
||||
@@ -4076,13 +4161,18 @@ function startOdysseusApp() {
|
||||
e.preventDefault();
|
||||
// Flush the debounced icon update so dataset.mode reflects the current
|
||||
// text state. Without this, a fast type-and-Enter would still see the
|
||||
// stale 'newchat' mode and open a new chat instead of sending.
|
||||
try { _updateSendBtnIcon(); } catch {}
|
||||
if (sendBtn && sendBtn.dataset.mode === 'newchat') {
|
||||
const railNew = el('rail-new-session');
|
||||
if (railNew) railNew.click();
|
||||
return;
|
||||
}
|
||||
// stale 'newchat' mode and open a new chat instead of sending.
|
||||
try { _updateSendBtnIcon(); } catch {}
|
||||
if (sendBtn && sendBtn.dataset.mode === 'newchat') {
|
||||
const isAlreadyNewChat = document.getElementById('chat-container')?.classList.contains('welcome-active');
|
||||
if (isAlreadyNewChat) {
|
||||
if (messageInput) messageInput.focus();
|
||||
return;
|
||||
}
|
||||
const railNew = el('rail-new-session');
|
||||
if (railNew) railNew.click();
|
||||
return;
|
||||
}
|
||||
if (_isForegroundChatBusy() && messageInput.value && messageInput.value.trim()) {
|
||||
if (chatModule && chatModule.queueStreamingComposerRequest && chatModule.queueStreamingComposerRequest()) {
|
||||
return;
|
||||
|
||||
Reference in New Issue
Block a user