diff --git a/src/routes/chat/[agentId]/[conversationId]/chat-box.svelte b/src/routes/chat/[agentId]/[conversationId]/chat-box.svelte index bb0c6050..1a46a9bc 100644 --- a/src/routes/chat/[agentId]/[conversationId]/chat-box.svelte +++ b/src/routes/chat/[agentId]/[conversationId]/chat-box.svelte @@ -1653,11 +1653,19 @@ } } - /** @param {string} messageId */ + /** + * Clicking a user message marks it as the one being inspected, and points the + * log panes at it. The highlight is unconditional — it is feedback that the + * click landed, and the message stays picked out whether or not the logs are + * on screen. Directing the panes only makes sense while they are open. + * @param {string} messageId + */ function directToLog(messageId) { - if (!messageId || isLite || !isLoadPersistLog) return; + if (!messageId || isLite) return; highlightedMsgId = messageId; + if (!isLoadPersistLog) return; + highlightStateLog(messageId); autoScrollToTargetLog(messageId); } @@ -1682,6 +1690,9 @@ /** @param {string} messageId */ function autoScrollToTargetLog(messageId) { + // Tell a freshly opened log pane to stop pinning itself to the tail, or it + // would pull straight back down from the entry we are about to show. + window.dispatchEvent(new CustomEvent('persist-log:cancel-pin')); const contentLogWrapper = '.content-log-scrollbar'; const stateLogWrapper = '.conv-state-log-scrollbar'; const elements = []; diff --git a/src/routes/chat/[agentId]/[conversationId]/persist-log/persist-log.svelte b/src/routes/chat/[agentId]/[conversationId]/persist-log/persist-log.svelte index 943a2883..931a22ef 100644 --- a/src/routes/chat/[agentId]/[conversationId]/persist-log/persist-log.svelte +++ b/src/routes/chat/[agentId]/[conversationId]/persist-log/persist-log.svelte @@ -14,6 +14,8 @@ const contentLogTab = 1; const conversationStateLogTab = 2; const conversationId = page.params.conversationId; + /** Fired by chat-box when it scrolls a pane to a specific log entry. */ + const CANCEL_PIN_EVENT = 'persist-log:cancel-pin'; const utcNow = moment.utc().toDate(); const scrollbarElements = [ @@ -178,6 +180,17 @@ * @param {number} timeoutMs */ function pinToBottomWhileSettling(timeoutMs = 3000) { + /** @type {(() => void)[]} */ + const stops = []; + /* + * Opening the panes to look at one message races the settling pin: the pin + * would drag the pane back to the tail moments after the jump. A scroll + * aimed at a specific entry cancels the pin, the same way a wheel does. + */ + const cancelPin = () => stops.forEach(stop => stop()); + window.addEventListener(CANCEL_PIN_EVENT, cancelPin); + stops.push(() => window.removeEventListener(CANCEL_PIN_EVENT, cancelPin)); + scrollbars.forEach(scrollbar => { if (!scrollbar) return; @@ -202,6 +215,7 @@ viewport.addEventListener('pointerdown', stop); viewport.addEventListener('keydown', stop); timer = setTimeout(stop, timeoutMs); + stops.push(stop); }); }