mirror of
https://github.com/pewdiepie-archdaemon/odysseus.git
synced 2026-07-12 12:37:32 +00:00
Polish mobile UI and editor workflows
This commit is contained in:
+321
-53
@@ -16,8 +16,11 @@ let sessions = [];
|
||||
let currentSessionId = null;
|
||||
let _sessionNavToken = 0;
|
||||
let _skipAutoSelect = false;
|
||||
let _suppressNextSessionLoading = false;
|
||||
const HISTORY_DISPLAY_CHAR_LIMIT = 160000;
|
||||
const HISTORY_DISPLAY_TAIL_CHARS = 20000;
|
||||
const HISTORY_PAGE_LIMIT_MOBILE = 8;
|
||||
const HISTORY_PAGE_LIMIT_DESKTOP = 24;
|
||||
|
||||
const SIDEBAR_MAX_VISIBLE = 10;
|
||||
const FOLDER_MAX_VISIBLE = 5;
|
||||
@@ -28,41 +31,42 @@ let _autoCreateInProgress = false; // guard against recursive auto-create
|
||||
const _INCOGNITO_SESSIONS_KEY = 'ody-incognito-sessions'; // sessionStorage key for incognito session IDs
|
||||
const _isMac = /Mac|iPhone|iPad/.test(navigator.platform);
|
||||
const _mod = _isMac ? '⌘' : 'Ctrl';
|
||||
let _historyPager = null;
|
||||
|
||||
function _paintSessionLoading(chatHistory, label = 'Loading chat') {
|
||||
if (!chatHistory) return;
|
||||
if (chatRenderer.hideWelcomeScreen) chatRenderer.hideWelcomeScreen();
|
||||
chatHistory.style.transition = '';
|
||||
chatHistory.style.opacity = '1';
|
||||
chatHistory.classList.add('no-animate');
|
||||
chatHistory.innerHTML = '';
|
||||
|
||||
const wrap = document.createElement('div');
|
||||
wrap.className = 'session-loading-state';
|
||||
wrap.className = 'session-loading-state session-loading-skeleton';
|
||||
wrap.setAttribute('role', 'status');
|
||||
wrap.setAttribute('aria-live', 'polite');
|
||||
wrap.style.cssText = [
|
||||
'min-height:100%',
|
||||
'display:flex',
|
||||
'align-items:center',
|
||||
'justify-content:center',
|
||||
'gap:10px',
|
||||
'color:var(--muted, var(--fg))',
|
||||
'opacity:0.72',
|
||||
'font-size:12px'
|
||||
].join(';');
|
||||
wrap.setAttribute('aria-label', label);
|
||||
|
||||
const spinner = spinnerModule.createWhirlpool(18);
|
||||
const text = document.createElement('span');
|
||||
text.className = 'session-loading-state-label';
|
||||
text.textContent = label;
|
||||
wrap.appendChild(spinner.element);
|
||||
wrap.appendChild(text);
|
||||
const viewportHeight = chatHistory.clientHeight || window.innerHeight || 720;
|
||||
const bubbleCount = Math.max(8, Math.min(16, Math.ceil(viewportHeight / 86)));
|
||||
for (let i = 0; i < bubbleCount; i += 1) {
|
||||
const bubble = document.createElement('div');
|
||||
bubble.className = `session-skeleton-bubble ${i % 2 ? 'is-user' : 'is-ai'}`;
|
||||
const lines = i % 4 === 1 ? 2 : (i % 4 === 3 ? 3 : 4);
|
||||
for (let j = 0; j < lines; j += 1) {
|
||||
const line = document.createElement('div');
|
||||
line.className = 'session-skeleton-line';
|
||||
line.style.width = `${[72, 92, 58, 82][(i + j) % 4]}%`;
|
||||
bubble.appendChild(line);
|
||||
}
|
||||
wrap.appendChild(bubble);
|
||||
}
|
||||
chatHistory.appendChild(wrap);
|
||||
}
|
||||
|
||||
function _updateSessionLoading(chatHistory, label) {
|
||||
const el = chatHistory?.querySelector('.session-loading-state-label');
|
||||
if (el) el.textContent = label;
|
||||
const el = chatHistory?.querySelector('.session-loading-state');
|
||||
if (el) el.setAttribute('aria-label', label);
|
||||
}
|
||||
|
||||
function _nextPaint() {
|
||||
@@ -88,6 +92,181 @@ function _displayHistoryContent(content) {
|
||||
].join('\n');
|
||||
}
|
||||
|
||||
function _historyPageLimit() {
|
||||
return window.innerWidth <= 768 ? HISTORY_PAGE_LIMIT_MOBILE : HISTORY_PAGE_LIMIT_DESKTOP;
|
||||
}
|
||||
|
||||
function _historyUrl(id, { limit = null, offset = null } = {}) {
|
||||
const url = new URL(`${API_BASE}/api/history/${id}`);
|
||||
if (limit != null) url.searchParams.set('limit', String(limit));
|
||||
if (offset != null) url.searchParams.set('offset', String(offset));
|
||||
return url.toString();
|
||||
}
|
||||
|
||||
function _renderHistoryMessage(msg, modelName) {
|
||||
const meta = msg.metadata ? { ...msg.metadata, _fromHistory: true } : null;
|
||||
let displayContent;
|
||||
if (typeof msg.content === 'string') {
|
||||
displayContent = _displayHistoryContent(msg.content);
|
||||
} else if (Array.isArray(msg.content)) {
|
||||
displayContent = _displayHistoryContent(msg.content.filter(p => p.type === 'text').map(p => p.text).join('\n').trim());
|
||||
} else {
|
||||
displayContent = '';
|
||||
}
|
||||
if (msg.role === 'user') {
|
||||
const trimmed = displayContent.trim();
|
||||
if (
|
||||
trimmed === 'Continue where you left off' ||
|
||||
trimmed.startsWith('Your message was cut off.') ||
|
||||
trimmed.startsWith('Your previous response was interrupted.') ||
|
||||
displayContent.includes('[Instruction: Rewrite') ||
|
||||
displayContent.includes('[Instruction: Explain')
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
const docEditMatch = displayContent.match(/^In the document, edit this specific text \((lines? [\d-]+)\):\n```\n([\s\S]*?)\n```\n\nInstruction: ([\s\S]*)$/);
|
||||
if (docEditMatch) {
|
||||
displayContent = `[Doc edit: ${docEditMatch[1]}] ${docEditMatch[3]}`;
|
||||
}
|
||||
}
|
||||
const box = document.getElementById('chat-history');
|
||||
if (!box) return null;
|
||||
if (chatRenderer.hideWelcomeScreen) chatRenderer.hideWelcomeScreen();
|
||||
|
||||
const wrap = document.createElement('div');
|
||||
wrap.className = 'msg ' + (msg.role === 'user' ? 'msg-user' : 'msg-ai');
|
||||
wrap.dataset.raw = displayContent;
|
||||
if (meta?._db_id) wrap.dataset.dbId = meta._db_id;
|
||||
|
||||
const roleEl = document.createElement('div');
|
||||
roleEl.className = 'role';
|
||||
if (msg.role === 'user') {
|
||||
roleEl.textContent = 'You';
|
||||
} else {
|
||||
const pair = chatRenderer.replyModelPair ? chatRenderer.replyModelPair(modelName, meta) : {};
|
||||
const resolved = pair.actualModel || pair.requestedModel || modelName;
|
||||
roleEl.textContent = chatRenderer.modelRouteLabel
|
||||
? chatRenderer.modelRouteLabel(pair.requestedModel, resolved)
|
||||
: (resolved || 'Odysseus');
|
||||
if (chatRenderer.applyModelColor) chatRenderer.applyModelColor(roleEl, resolved);
|
||||
}
|
||||
const timestamp = meta?.timestamp;
|
||||
if (timestamp) {
|
||||
const ts = document.createElement('span');
|
||||
ts.className = 'msg-time';
|
||||
try {
|
||||
ts.textContent = new Date(timestamp).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
|
||||
} catch {
|
||||
ts.textContent = '';
|
||||
}
|
||||
roleEl.appendChild(ts);
|
||||
}
|
||||
|
||||
const body = document.createElement('div');
|
||||
body.className = 'body';
|
||||
body.innerHTML = markdownModule.processWithThinking(
|
||||
markdownModule.squashOutsideCode(markdownModule.renderContent(displayContent || ''))
|
||||
);
|
||||
if (msg.role === 'user' && Array.isArray(meta?.attachments) && meta.attachments.length) {
|
||||
const cards = document.createElement('div');
|
||||
cards.className = 'attach-cards history-attach-cards';
|
||||
for (const att of meta.attachments) {
|
||||
const card = document.createElement('div');
|
||||
card.className = 'attach-card history-attach-card';
|
||||
const icon = document.createElement('span');
|
||||
icon.className = 'attach-card-icon';
|
||||
icon.innerHTML = '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><rect x="3" y="3" width="18" height="18" rx="2"/><circle cx="8.5" cy="8.5" r="1.5"/><path d="M21 15l-5-5L5 21"/></svg>';
|
||||
const name = document.createElement('span');
|
||||
name.className = 'attach-card-name';
|
||||
name.textContent = att.name || 'Image attached';
|
||||
const size = document.createElement('span');
|
||||
size.className = 'attach-card-size';
|
||||
size.textContent = 'image';
|
||||
card.appendChild(icon);
|
||||
card.appendChild(name);
|
||||
card.appendChild(size);
|
||||
cards.appendChild(card);
|
||||
}
|
||||
body.appendChild(cards);
|
||||
}
|
||||
|
||||
wrap.appendChild(roleEl);
|
||||
wrap.appendChild(body);
|
||||
box.appendChild(wrap);
|
||||
return wrap;
|
||||
}
|
||||
|
||||
function _clearHistoryPager() {
|
||||
const box = document.getElementById('chat-history');
|
||||
if (_historyPager?.handler && box) {
|
||||
box.removeEventListener('scroll', _historyPager.handler);
|
||||
}
|
||||
_historyPager = null;
|
||||
}
|
||||
|
||||
function _installHistoryPager(id, pageInfo, modelName) {
|
||||
const box = document.getElementById('chat-history');
|
||||
_clearHistoryPager();
|
||||
if (!box || !pageInfo || !pageInfo.has_more_before) return;
|
||||
|
||||
_historyPager = {
|
||||
sessionId: id,
|
||||
offset: Number(pageInfo.offset || 0),
|
||||
limit: Number(pageInfo.limit || _historyPageLimit()),
|
||||
loading: false,
|
||||
done: false,
|
||||
modelName,
|
||||
handler: null,
|
||||
};
|
||||
|
||||
const loadOlder = async () => {
|
||||
if (!_historyPager || _historyPager.loading || _historyPager.done) return;
|
||||
if (_historyPager.sessionId !== currentSessionId) return;
|
||||
if (box.scrollTop > 90) return;
|
||||
|
||||
const nextOffset = Math.max(0, _historyPager.offset - _historyPager.limit);
|
||||
const nextLimit = _historyPager.offset - nextOffset;
|
||||
if (nextLimit <= 0) {
|
||||
_historyPager.done = true;
|
||||
return;
|
||||
}
|
||||
|
||||
_historyPager.loading = true;
|
||||
const anchor = box.querySelector('.msg, .agent-thread, .gallery-bubble');
|
||||
const beforeHeight = box.scrollHeight;
|
||||
try {
|
||||
const res = await fetch(_historyUrl(_historyPager.sessionId, { limit: nextLimit, offset: nextOffset }));
|
||||
const data = await res.json();
|
||||
if (!_historyPager || _historyPager.sessionId !== currentSessionId) return;
|
||||
const newEls = [];
|
||||
for (const msg of data.history || []) {
|
||||
if (msg.role !== 'user' && msg.role !== 'assistant') continue;
|
||||
const el = _renderHistoryMessage(msg, _historyPager.modelName);
|
||||
if (el) newEls.push(el);
|
||||
}
|
||||
for (const el of newEls) {
|
||||
box.insertBefore(el, anchor || box.firstChild);
|
||||
}
|
||||
_historyPager.offset = Number(data.offset || nextOffset);
|
||||
_historyPager.done = !data.has_more_before;
|
||||
if (window.hljs) {
|
||||
newEls.forEach(el => el.querySelectorAll('pre code:not(.hljs)').forEach(block => window.hljs.highlightElement(block)));
|
||||
}
|
||||
const heightDelta = box.scrollHeight - beforeHeight;
|
||||
box.scrollTop += heightDelta;
|
||||
} catch (e) {
|
||||
console.warn('Failed to load older chat history:', e);
|
||||
} finally {
|
||||
if (_historyPager) _historyPager.loading = false;
|
||||
}
|
||||
};
|
||||
|
||||
_historyPager.handler = () => {
|
||||
if (box.scrollTop <= 90) loadOlder();
|
||||
};
|
||||
box.addEventListener('scroll', _historyPager.handler, { passive: true });
|
||||
}
|
||||
|
||||
function _getIncognitoIds() {
|
||||
try { return JSON.parse(sessionStorage.getItem(_INCOGNITO_SESSIONS_KEY) || '[]'); } catch { return []; }
|
||||
}
|
||||
@@ -803,6 +982,58 @@ function createSessionItem(s) {
|
||||
return div;
|
||||
}
|
||||
|
||||
function _dateBucketLabel(value) {
|
||||
if (!value) return 'Older';
|
||||
const d = new Date(value);
|
||||
if (Number.isNaN(d.getTime())) return 'Older';
|
||||
const dayStart = (x) => new Date(x.getFullYear(), x.getMonth(), x.getDate()).getTime();
|
||||
const today = dayStart(new Date());
|
||||
const day = dayStart(d);
|
||||
const diff = Math.round((today - day) / 86400000);
|
||||
if (diff === 0) return 'Today';
|
||||
if (diff === 1) return 'Yesterday';
|
||||
if (diff > 1 && diff < 7) return d.toLocaleDateString([], { weekday: 'long' });
|
||||
if (diff >= 365) {
|
||||
const years = Math.floor(diff / 365);
|
||||
return `${years} ${years === 1 ? 'year' : 'years'} ago`;
|
||||
}
|
||||
if (diff >= 180) return '6 months ago';
|
||||
if (diff >= 30) return `${Math.floor(diff / 30) * 30} days ago`;
|
||||
const sameYear = d.getFullYear() === new Date().getFullYear();
|
||||
return d.toLocaleDateString([], sameYear ? { month: 'long', day: 'numeric' } : { month: 'long', day: 'numeric', year: 'numeric' });
|
||||
}
|
||||
|
||||
function _sessionBucketDate(s) {
|
||||
return s.last_message_at || s.updated_at || s.created_at || '';
|
||||
}
|
||||
|
||||
function _createDateSectionHeader(label, kind = 'session') {
|
||||
const el = document.createElement('div');
|
||||
el.className = `date-section-header ${kind}-date-section-header`;
|
||||
el.textContent = label;
|
||||
return el;
|
||||
}
|
||||
|
||||
function _appendSessionItemsWithDateHeaders(frag, items) {
|
||||
let lastLabel = null;
|
||||
for (const s of items) {
|
||||
const label = _dateBucketLabel(_sessionBucketDate(s));
|
||||
if (label !== lastLabel) {
|
||||
frag.appendChild(_createDateSectionHeader(label, 'session'));
|
||||
lastLabel = label;
|
||||
}
|
||||
frag.appendChild(createSessionItem(s));
|
||||
}
|
||||
}
|
||||
|
||||
function _appendFavoriteSessionItems(frag, items) {
|
||||
if (!items.length) return;
|
||||
frag.appendChild(_createDateSectionHeader('Favorites', 'session'));
|
||||
for (const s of items) {
|
||||
frag.appendChild(createSessionItem(s));
|
||||
}
|
||||
}
|
||||
|
||||
let _renderRAF = null;
|
||||
export function renderSessionList() {
|
||||
// Debounce rapid re-renders within the same frame
|
||||
@@ -861,17 +1092,22 @@ function _renderSessionListImpl() {
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
// Starred still float to top
|
||||
const starred = orderedSessions.filter(s => s.is_important);
|
||||
const rest = orderedSessions.filter(s => !s.is_important);
|
||||
const allFlat = [...starred, ...rest];
|
||||
// Favorites are a global pinned block above date buckets, not just
|
||||
// promoted within the day they belong to.
|
||||
const allFlat = [
|
||||
...orderedSessions.filter(s => s.is_important),
|
||||
...orderedSessions.filter(s => !s.is_important),
|
||||
];
|
||||
|
||||
const limit = _showAllSessions ? allFlat.length : SIDEBAR_MAX_VISIBLE;
|
||||
const visible = allFlat.slice(0, limit);
|
||||
const activeIdx = allFlat.findIndex(s => s.id === currentSessionId);
|
||||
if (!_showAllSessions && activeIdx >= limit) visible.push(allFlat[activeIdx]);
|
||||
|
||||
visible.forEach(s => _frag.appendChild(createSessionItem(s)));
|
||||
const visibleFavorites = visible.filter(s => s.is_important);
|
||||
const visibleRegular = visible.filter(s => !s.is_important);
|
||||
_appendFavoriteSessionItems(_frag, visibleFavorites);
|
||||
_appendSessionItemsWithDateHeaders(_frag, visibleRegular);
|
||||
|
||||
if (allFlat.length > SIDEBAR_MAX_VISIBLE) {
|
||||
const remaining = allFlat.length - SIDEBAR_MAX_VISIBLE;
|
||||
@@ -1510,8 +1746,12 @@ export async function loadSessions() {
|
||||
}
|
||||
}
|
||||
|
||||
const suppressSessionLoading = _suppressNextSessionLoading;
|
||||
_suppressNextSessionLoading = false;
|
||||
|
||||
if (targetId && targetId !== currentSessionId) {
|
||||
await selectSession(targetId, { keepSidebar: true });
|
||||
const showLoading = !suppressSessionLoading && !(_isFirstLoad && !hashId);
|
||||
await selectSession(targetId, { keepSidebar: true, showLoading });
|
||||
} else if (targetId && targetId === currentSessionId) {
|
||||
// Same session — just refresh the header name in case it was auto-generated
|
||||
const s = sessions.find(x => x.id === targetId);
|
||||
@@ -1549,7 +1789,7 @@ export async function loadSessions() {
|
||||
}
|
||||
}
|
||||
|
||||
export async function selectSession(id, { keepSidebar = false } = {}) {
|
||||
export async function selectSession(id, { keepSidebar = false, showLoading = true } = {}) {
|
||||
// Exit compare mode cleanly if active
|
||||
if (window.compareModule && window.compareModule.isActive()) {
|
||||
window.compareModule.deactivate(true);
|
||||
@@ -1558,6 +1798,7 @@ export async function selectSession(id, { keepSidebar = false } = {}) {
|
||||
try {
|
||||
const navToken = ++_sessionNavToken;
|
||||
const prevSessionId = currentSessionId;
|
||||
_clearHistoryPager();
|
||||
// Re-archive peeked session when navigating away
|
||||
_checkPeekCleanup(id);
|
||||
// Clear any leftover document text selection so it doesn't bleed into the new chat
|
||||
@@ -1625,7 +1866,15 @@ export async function selectSession(id, { keepSidebar = false } = {}) {
|
||||
if (window._updateSendBtnIcon) window._updateSendBtnIcon();
|
||||
}
|
||||
|
||||
// On mobile, keep sidebar open — user dismisses it by tapping chat area or swiping
|
||||
// On mobile manual chat switches, move the drawer away before showing the
|
||||
// loader so the status sits over the chat pane instead of being hidden by
|
||||
// the sidebar. Startup auto-restore passes keepSidebar + showLoading=false.
|
||||
if (showLoading && !keepSidebar && window.innerWidth <= 768) {
|
||||
const sidebar = document.getElementById('sidebar');
|
||||
const backdrop = document.getElementById('sidebar-backdrop');
|
||||
if (sidebar) sidebar.classList.add('hidden');
|
||||
if (backdrop) backdrop.classList.remove('visible');
|
||||
}
|
||||
|
||||
// Highlight active session in sidebar
|
||||
document.querySelectorAll('.list-item.active-session').forEach(el => el.classList.remove('active-session'));
|
||||
@@ -1649,20 +1898,38 @@ export async function selectSession(id, { keepSidebar = false } = {}) {
|
||||
// declaration had been removed while leaving the references in
|
||||
// place, producing a ReferenceError every selectSession.)
|
||||
const isOC = meta && (meta.is_openclaw || id === 'openclaw');
|
||||
let msgHistory = [], modelName = null;
|
||||
let msgHistory = [], modelName = null, pageInfo = null;
|
||||
let paintedLoading = false;
|
||||
let loadingTimer = null;
|
||||
let loadingPaintReady = Promise.resolve();
|
||||
if (!isOC) {
|
||||
if (chatHistory && prevSessionId !== id) {
|
||||
_paintSessionLoading(chatHistory, 'Loading chat');
|
||||
paintedLoading = true;
|
||||
await _nextPaint();
|
||||
if (navToken !== _sessionNavToken || currentSessionId !== id) return;
|
||||
if (showLoading && chatHistory && prevSessionId !== id) {
|
||||
const loadingDelayMs = window.innerWidth <= 768 ? 900 : 500;
|
||||
loadingTimer = setTimeout(() => {
|
||||
if (navToken !== _sessionNavToken || currentSessionId !== id) return;
|
||||
_paintSessionLoading(chatHistory, 'Loading chat');
|
||||
paintedLoading = true;
|
||||
loadingPaintReady = _nextPaint();
|
||||
}, loadingDelayMs);
|
||||
}
|
||||
const res = await fetch(`${API_BASE}/api/history/${id}`);
|
||||
const res = await fetch(_historyUrl(id, { limit: _historyPageLimit() }));
|
||||
const data = await res.json();
|
||||
if (loadingTimer) {
|
||||
clearTimeout(loadingTimer);
|
||||
loadingTimer = null;
|
||||
}
|
||||
if (paintedLoading) {
|
||||
await loadingPaintReady;
|
||||
}
|
||||
if (navToken !== _sessionNavToken || currentSessionId !== id) return;
|
||||
msgHistory = data.history || [];
|
||||
modelName = data.model || null;
|
||||
pageInfo = {
|
||||
offset: data.offset,
|
||||
limit: data.limit,
|
||||
total: data.total,
|
||||
has_more_before: !!data.has_more_before,
|
||||
};
|
||||
// The model returned by /api/history is the authoritative one the
|
||||
// backend will use for this session. Write it back into the cached
|
||||
// session meta and refresh the picker so the displayed model can
|
||||
@@ -1721,26 +1988,11 @@ export async function selectSession(id, { keepSidebar = false } = {}) {
|
||||
'OpenClaw');
|
||||
} else if (msgHistory.length) {
|
||||
for (const msg of msgHistory) {
|
||||
const meta = msg.metadata ? { ...msg.metadata, _fromHistory: true } : null;
|
||||
let displayContent;
|
||||
if (typeof msg.content === 'string') {
|
||||
displayContent = _displayHistoryContent(msg.content);
|
||||
} else if (Array.isArray(msg.content)) {
|
||||
// Multimodal (image/audio attachments): extract text parts, skip binary
|
||||
displayContent = _displayHistoryContent(msg.content.filter(p => p.type === 'text').map(p => p.text).join('\n').trim());
|
||||
} else {
|
||||
displayContent = '';
|
||||
try {
|
||||
_renderHistoryMessage(msg, modelName);
|
||||
} catch (e) {
|
||||
console.warn('Failed to render history message:', e, msg);
|
||||
}
|
||||
// Clean up doc selection context for display
|
||||
if (msg.role === 'user') {
|
||||
// Hide "Continue where you left off" bubbles
|
||||
if (displayContent.trim() === 'Continue where you left off' || displayContent.trim().startsWith('Your message was cut off.') || displayContent.trim().startsWith('Your previous response was interrupted.') || displayContent.includes('[Instruction: Rewrite') || displayContent.includes('[Instruction: Explain')) continue;
|
||||
const docEditMatch = displayContent.match(/^In the document, edit this specific text \((lines? [\d-]+)\):\n```\n([\s\S]*?)\n```\n\nInstruction: ([\s\S]*)$/);
|
||||
if (docEditMatch) {
|
||||
displayContent = `[Doc edit: ${docEditMatch[1]}] ${docEditMatch[3]}`;
|
||||
}
|
||||
}
|
||||
window.chatModule.addMessage(msg.role, markdownModule.renderContent(displayContent), modelName, meta);
|
||||
}
|
||||
} else {
|
||||
if (window.chatModule && window.chatModule.showWelcomeScreen) window.chatModule.showWelcomeScreen();
|
||||
@@ -1748,6 +2000,9 @@ export async function selectSession(id, { keepSidebar = false } = {}) {
|
||||
document.querySelectorAll('.list-item.active-session').forEach(el => el.classList.remove('active-session'));
|
||||
}
|
||||
uiModule.scrollHistoryInstant();
|
||||
if (!isOC && msgHistory.length) {
|
||||
_installHistoryPager(id, pageInfo, modelName);
|
||||
}
|
||||
|
||||
// Fade in and re-enable message animations
|
||||
if (chatHistory) {
|
||||
@@ -1817,6 +2072,16 @@ export async function selectSession(id, { keepSidebar = false } = {}) {
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error in selectSession:', error);
|
||||
const chatHistory = uiModule.el('chat-history');
|
||||
if (chatHistory?.querySelector('.session-loading-state')) {
|
||||
chatHistory.innerHTML = '';
|
||||
chatHistory.style.opacity = '1';
|
||||
chatHistory.classList.remove('no-animate');
|
||||
const msg = document.createElement('div');
|
||||
msg.className = 'msg msg-ai';
|
||||
msg.innerHTML = `<div class="body">Failed to load this chat. ${uiModule.esc ? uiModule.esc(error.message || '') : ''}</div>`;
|
||||
chatHistory.appendChild(msg);
|
||||
}
|
||||
uiModule.showError('Failed to load session: ' + error.message);
|
||||
} finally {
|
||||
// Ensure memories are loaded after session selection
|
||||
@@ -1854,6 +2119,7 @@ export function createDirectChat(url, modelId, endpointId) {
|
||||
// Don't hit the API — just store the model info and prepare the UI
|
||||
_pendingChat = { url, modelId, endpointId };
|
||||
_skipAutoSelect = true;
|
||||
_suppressNextSessionLoading = true;
|
||||
currentSessionId = null;
|
||||
Storage.remove('lastSessionId');
|
||||
history.replaceState(null, '', window.location.pathname);
|
||||
@@ -1950,6 +2216,7 @@ export async function materializePendingSession() {
|
||||
|
||||
// Reload sidebar to show the new session — await it so the session
|
||||
// is fully registered before the caller proceeds (prevents race conditions)
|
||||
_suppressNextSessionLoading = true;
|
||||
await loadSessions().catch(() => {});
|
||||
return true;
|
||||
}
|
||||
@@ -1986,6 +2253,7 @@ export function setCurrentSessionId(id) {
|
||||
_sessionNavToken++;
|
||||
currentSessionId = id;
|
||||
if (!id) {
|
||||
_suppressNextSessionLoading = true;
|
||||
Storage.remove('lastSessionId');
|
||||
history.replaceState(null, '', window.location.pathname);
|
||||
document.querySelectorAll('.list-item.active-session, .session-item.active').forEach(el => {
|
||||
|
||||
Reference in New Issue
Block a user