diff --git a/application/single_app/config.py b/application/single_app/config.py
index 30cb1b95f..b3419b568 100644
--- a/application/single_app/config.py
+++ b/application/single_app/config.py
@@ -94,7 +94,7 @@
EXECUTOR_TYPE = 'thread'
EXECUTOR_MAX_WORKERS = 30
SESSION_TYPE = 'filesystem'
-VERSION = "0.241.007"
+VERSION = "0.241.008"
SECRET_KEY = os.getenv('SECRET_KEY', 'dev-secret-key-change-in-production')
diff --git a/application/single_app/static/js/chat/chat-global.js b/application/single_app/static/js/chat/chat-global.js
index 0cb42f724..7ea874a46 100644
--- a/application/single_app/static/js/chat/chat-global.js
+++ b/application/single_app/static/js/chat/chat-global.js
@@ -9,9 +9,71 @@ let groupPrompts = [];
let publicPrompts = [];
let currentlyEditingId = null;
+function getChatScrollContainer() {
+ return (
+ document.getElementById("chat-messages-container") ||
+ document.getElementById("chatbox") ||
+ null
+ );
+}
+
+function isChatNearBottom(threshold = 40) {
+ const container = getChatScrollContainer();
+ if (!container) return true;
+
+ const distanceFromBottom =
+ container.scrollHeight - (container.scrollTop + container.clientHeight);
+ return distanceFromBottom <= threshold;
+}
+
function scrollChatToBottom() {
- const chatbox = document.getElementById("chatbox");
- if (chatbox) {
- chatbox.scrollTop = chatbox.scrollHeight;
+ const container = getChatScrollContainer();
+ if (container) {
+ container.scrollTop = container.scrollHeight;
}
-}
\ No newline at end of file
+}
+
+function showScrollToBottomButton() {
+ const btn = document.getElementById("scroll-to-bottom-btn");
+ if (!btn) return;
+ btn.classList.remove("d-none");
+}
+
+function hideScrollToBottomButton() {
+ const btn = document.getElementById("scroll-to-bottom-btn");
+ if (!btn) return;
+ btn.classList.add("d-none");
+}
+
+function initializeChatScrollBehavior() {
+ const container = getChatScrollContainer();
+ const btn = document.getElementById("scroll-to-bottom-btn");
+
+ if (!container) return;
+
+ // Initial state
+ if (isChatNearBottom()) {
+ hideScrollToBottomButton();
+ } else {
+ showScrollToBottomButton();
+ }
+
+ container.addEventListener("scroll", () => {
+ if (isChatNearBottom()) {
+ hideScrollToBottomButton();
+ } else {
+ showScrollToBottomButton();
+ }
+ });
+
+ if (btn) {
+ btn.addEventListener("click", () => {
+ scrollChatToBottom();
+ hideScrollToBottomButton();
+ });
+ }
+}
+
+window.addEventListener("load", () => {
+ initializeChatScrollBehavior();
+});
\ No newline at end of file
diff --git a/application/single_app/static/js/chat/chat-messages.js b/application/single_app/static/js/chat/chat-messages.js
index c904639d6..7a6bb7586 100644
--- a/application/single_app/static/js/chat/chat-messages.js
+++ b/application/single_app/static/js/chat/chat-messages.js
@@ -1036,7 +1036,14 @@ export function appendMessage(
});
}
- scrollChatToBottom();
+ // For AI messages, only auto-scroll if the user is currently near
+ // the bottom. This prevents a final jump after a long answer if
+ // the user has scrolled up to read earlier content.
+ if (typeof isChatNearBottom === 'function' && typeof scrollChatToBottom === 'function') {
+ if (isChatNearBottom()) {
+ scrollChatToBottom();
+ }
+ }
return; // <<< EXIT EARLY FOR AI MESSAGES
// --- Handle ALL OTHER message types ---
@@ -1106,7 +1113,10 @@ export function appendMessage(
// Validate image URL before creating img tag
if (messageContent && messageContent !== 'null' && messageContent.trim() !== '') {
- messageContentHtml = ``;
+ // Use a placeholder container; the actual
element will be
+ // created with DOM APIs after insertion to avoid string-based
+ // attribute interpolation in src/data-*.
+ messageContentHtml = '';
} else {
messageContentHtml = `