diff --git a/application/single_app/config.py b/application/single_app/config.py index 0296c2451..2dc13e9a2 100644 --- a/application/single_app/config.py +++ b/application/single_app/config.py @@ -97,7 +97,7 @@ EXECUTOR_TYPE = 'thread' EXECUTOR_MAX_WORKERS = 30 SESSION_TYPE = 'filesystem' -VERSION = "0.250.072" +VERSION = "0.250.073" IS_DEVELOPMENT = is_development_env_enabled() SESSION_COOKIE_SAMESITE = os.getenv('SESSION_COOKIE_SAMESITE', 'Lax') diff --git a/application/single_app/functions_settings.py b/application/single_app/functions_settings.py index 9acbf63a9..709a6f908 100644 --- a/application/single_app/functions_settings.py +++ b/application/single_app/functions_settings.py @@ -27,6 +27,8 @@ USER_SETTINGS_REQUEST_CACHE_ATTR = "simplechat_user_settings_request_cache" +FONT_SIZE_PREFERENCES = ("xs", "s", "m", "l", "xl") +DEFAULT_FONT_SIZE_PREFERENCE = "m" USER_UI_SETTINGS_KEYS = ( "profileImage", "navLayout", @@ -38,6 +40,7 @@ "sidebarToggleStyle", "sidebarMenuState", LATEST_FEATURES_HIDDEN_VERSION_SETTING, + "fontSizePreference", ) ADMIN_SETTINGS_SECRET_REDACTED_VALUE = "***REDACTED***" ADMIN_SETTINGS_FORM_SECRET_FIELDS = ( @@ -129,6 +132,13 @@ def _clone_user_settings_doc(doc): return copy.deepcopy(doc or {}) +def normalize_font_size_preference(value): + normalized_value = str(value or "").strip().lower() + if normalized_value in FONT_SIZE_PREFERENCES: + return normalized_value + return DEFAULT_FONT_SIZE_PREFERENCE + + def _get_user_settings_request_cache(): if not has_request_context(): return None @@ -163,11 +173,15 @@ def _extract_user_ui_settings(doc): settings = (doc or {}).get('settings', {}) if not isinstance(settings, dict): settings = {} - return { + ui_settings = { key: copy.deepcopy(settings[key]) for key in USER_UI_SETTINGS_KEYS if key in settings } + ui_settings["fontSizePreference"] = normalize_font_size_preference( + settings.get("fontSizePreference") + ) + return ui_settings def _delete_user_ui_settings_cache(user_id): diff --git a/application/single_app/route_backend_users.py b/application/single_app/route_backend_users.py index 643acac61..f33d0accc 100644 --- a/application/single_app/route_backend_users.py +++ b/application/single_app/route_backend_users.py @@ -506,7 +506,7 @@ def user_settings(): # Chat UI settings 'navbar_layout', 'chatLayout', 'showChatTitle', 'chatSplitSizes', 'deepResearchDefaultEnabled', - 'sidebarToggleStyle', 'sidebarMenuState', + 'sidebarToggleStyle', 'sidebarMenuState', 'fontSizePreference', LATEST_FEATURES_HIDDEN_VERSION_SETTING, # Microphone permission settings 'microphonePermissionPreference', 'microphonePermissionState', @@ -542,6 +542,14 @@ def user_settings(): return jsonify({"error": "Invalid sidebar toggle style"}), 400 settings_to_update["sidebarToggleStyle"] = sidebar_toggle_style + if "fontSizePreference" in settings_to_update: + font_size_preference = str( + settings_to_update.get("fontSizePreference") or "" + ).strip().lower() + if font_size_preference not in FONT_SIZE_PREFERENCES: + return jsonify({"error": "Invalid font size preference"}), 400 + settings_to_update["fontSizePreference"] = font_size_preference + if "sidebarMenuState" in settings_to_update: sidebar_menu_state = settings_to_update.get("sidebarMenuState") allowed_sidebar_menu_keys = { diff --git a/application/single_app/static/css/chats.css b/application/single_app/static/css/chats.css index d94a66f4e..1d0b80ef9 100644 --- a/application/single_app/static/css/chats.css +++ b/application/single_app/static/css/chats.css @@ -1,5 +1,52 @@ /* chats.css */ +body.chat-shell-page { + --chat-shell-top-offset: var(--top-nav-height, 66px); + height: 100dvh; + min-height: 0 !important; + overflow: hidden; +} + +body.chat-shell-page.has-classification-banner { + --chat-shell-top-offset: calc( + var(--top-nav-height, 66px) + var(--classification-banner-height, 40px) + ); +} + +body.chat-shell-page.sidebar-nav-enabled { + --chat-shell-top-offset: 0px; +} + +body.chat-shell-page.sidebar-nav-enabled.has-classification-banner { + --chat-shell-top-offset: var(--classification-banner-height, 40px); +} + +body.chat-shell-page.sidebar-nav-enabled { + padding-top: var(--chat-shell-top-offset) !important; +} + +body.chat-shell-page #main-content { + min-height: 0; +} + +body.chat-shell-page.sidebar-nav-enabled #main-content { + padding-top: 0 !important; +} + +body.chat-shell-page.sidebar-nav-enabled .chat-page-shell { + margin-left: 0 !important; + max-width: 100% !important; + padding-top: 0 !important; + width: 100% !important; +} + +.chat-page-shell { + height: calc(100vh - var(--chat-shell-top-offset)); + height: calc(100dvh - var(--chat-shell-top-offset)); + min-height: 0; + overflow: hidden; +} + /* Prevent initial layout shift by disabling transitions on page load */ .no-transitions *, .no-transitions #left-pane, @@ -57,7 +104,7 @@ } .chat-shell-status-row { - min-height: 1.5rem; + min-height: 0; } .assistant-follow-up-actions { @@ -881,6 +928,7 @@ img.chat-searchable-select-item-icon { #left-pane { height: 100%; /* Make panes fill the container height */ + min-height: 0; overflow: hidden; /* Prevent panes themselves from scrolling */ margin-right: 10px; /* Optional: space between left and right panes */ /* d-flex and flex-column are already on the elements via HTML classes for internal layout */ @@ -888,6 +936,7 @@ img.chat-searchable-select-item-icon { #right-pane { height: 100%; /* Make panes fill the container height */ + min-height: 0; overflow: hidden; /* Prevent panes themselves from scrolling */ margin-left: 30px; /* Optional: space between left and right panes */ /* d-flex and flex-column are already on the elements via HTML classes for internal layout */ @@ -1147,10 +1196,10 @@ img.chat-searchable-select-item-icon { /* Style when docked - CLEAN SIMPLE IMPLEMENTATION */ body.layout-docked #left-pane { position: fixed !important; - top: 66px !important; /* Below navbar */ + top: var(--chat-shell-top-offset) !important; left: 0 !important; width: 320px !important; - height: calc(100vh - 66px) !important; + height: calc(100dvh - var(--chat-shell-top-offset)) !important; z-index: 1031 !important; background: var(--bs-body-bg, white) !important; border-right: 1px solid var(--bs-border-color, #dee2e6) !important; @@ -1159,7 +1208,7 @@ body.layout-docked #left-pane { body.layout-docked #right-pane { margin-left: 320px !important; width: calc(100% - 320px) !important; - height: calc(100vh - 66px) !important; + height: calc(100dvh - var(--chat-shell-top-offset)) !important; position: relative !important; /* Center content horizontally */ display: flex !important; @@ -1173,16 +1222,6 @@ body.layout-docked #right-pane > div { max-width: 1000px !important; } -/* When classification banner is present */ -body.has-classification-banner.layout-docked #left-pane { - top: 106px !important; - height: calc(100vh - 106px) !important; -} - -body.has-classification-banner.layout-docked #right-pane { - height: calc(100vh - 106px) !important; -} - /* Hide Split.js gutter when docked */ body.layout-docked .gutter { display: none !important; @@ -1630,6 +1669,7 @@ body.layout-split .gutter { /* Chatbox styling */ #chatbox { padding: 5px; + min-height: 0; overflow-y: auto; overflow-x: clip; /* Prevent horizontal scroll but allow content to be visible */ flex-grow: 1; @@ -1644,6 +1684,44 @@ body.layout-split .gutter { display: flex; flex-direction: column; height: 100%; + min-height: 0; +} + +html[data-font-size="l"] .chat-shell-header, +html[data-font-size="xl"] .chat-shell-header { + padding: 0.5rem 0.75rem !important; +} + +html[data-font-size="l"] .chat-container > .border-top, +html[data-font-size="xl"] .chat-container > .border-top { + padding: 0.5rem 0.75rem !important; +} + +@media (max-height: 600px) { + .chat-shell-header { + padding: 0.375rem 0.75rem !important; + } + + .chat-container > .border-top { + padding: 0.375rem 0.75rem !important; + } + + .chat-toolbar { + gap: 0.375rem; + margin-bottom: 0.25rem !important; + } + + .chat-toolbar-controls { + gap: 0.375rem; + } + + #user-input { + min-height: 44px !important; + } + + .chat-toolbar-mobile-panel { + --bs-offcanvas-height: min(24rem, 90dvh); + } } .classification-container { diff --git a/application/single_app/static/css/navigation.css b/application/single_app/static/css/navigation.css index 866361ece..4ca6625a4 100644 --- a/application/single_app/static/css/navigation.css +++ b/application/single_app/static/css/navigation.css @@ -1,8 +1,8 @@ /* Top Navigation Styles */ :root { - --top-nav-height: 66px; - --classification-banner-height: 40px; + --top-nav-height: 4.125rem; + --classification-banner-height: max(40px, 2.5rem); } /* Base body padding for top navigation */ @@ -93,12 +93,12 @@ body { @media (max-width: 991.98px) { .top-nav-mobile-drawer.offcanvas-start { - height: calc(100vh - var(--top-nav-height)); + height: calc(100dvh - var(--top-nav-height)); top: var(--top-nav-height); } body.has-classification-banner .top-nav-mobile-drawer.offcanvas-start { - height: calc(100vh - var(--top-nav-height) - var(--classification-banner-height)); + height: calc(100dvh - var(--top-nav-height) - var(--classification-banner-height)); top: calc(var(--top-nav-height) + var(--classification-banner-height)); } } @@ -159,7 +159,7 @@ body { } .custom-pages-drawer-list { - max-height: calc(100vh - 9rem); + max-height: calc(100dvh - 9rem); overflow-y: auto; } @@ -337,7 +337,7 @@ body { @media (max-width: 991.98px) { :root { - --top-nav-height: 64px; + --top-nav-height: 4rem; } .top-nav-shell { diff --git a/application/single_app/static/css/sidebar.css b/application/single_app/static/css/sidebar.css index c103070d4..4dcba03a6 100644 --- a/application/single_app/static/css/sidebar.css +++ b/application/single_app/static/css/sidebar.css @@ -15,6 +15,7 @@ body.sidebar-nav-enabled { /* Sidebar container */ #sidebar-nav { display: none; + height: 100dvh !important; transition: transform 0.3s ease; } @@ -56,20 +57,20 @@ body.sidebar-nav-enabled .container-fluid { /* Classification banner adjustments for sidebar layout */ body.has-classification-banner #sidebar-nav { - top: 40px !important; /* Start below the classification banner */ - height: calc(100vh - 40px) !important; /* Adjust height to account for banner */ + top: var(--classification-banner-height) !important; + height: calc(100dvh - var(--classification-banner-height)) !important; } /* Chats top-nav layout: align the fixed sidebar just below the navbar */ nav.navbar.fixed-top + #sidebar-nav { top: var(--top-nav-height) !important; - height: calc(100vh - var(--top-nav-height)); + height: calc(100dvh - var(--top-nav-height)) !important; } /* Account for classification banner when present */ body.has-classification-banner nav.navbar + #sidebar-nav { top: calc(var(--top-nav-height) + var(--classification-banner-height)) !important; - height: calc(100vh - var(--top-nav-height) - var(--classification-banner-height)); + height: calc(100dvh - var(--top-nav-height) - var(--classification-banner-height)) !important; } body.chat-top-nav-shell { @@ -116,7 +117,7 @@ body.chat-top-nav-shell #sidebar-nav.chat-sidebar-nav .chat-sidebar-mobile-secti @media (min-width: 992px) { body.chat-top-nav-shell #sidebar-nav.chat-sidebar-nav { - height: calc(100vh - var(--chat-sidebar-top)); + height: calc(100dvh - var(--chat-sidebar-top)) !important; left: 0; max-width: none; position: fixed; @@ -131,7 +132,7 @@ body.chat-top-nav-shell #sidebar-nav.chat-sidebar-nav .chat-sidebar-mobile-secti @media (max-width: 991.98px) { body.chat-top-nav-shell #sidebar-nav.chat-sidebar-nav { - height: calc(100vh - var(--chat-sidebar-top)); + height: calc(100dvh - var(--chat-sidebar-top)) !important; max-width: min(88vw, 22rem); top: var(--chat-sidebar-top); z-index: 1045; @@ -155,7 +156,7 @@ body.chat-top-nav-shell #sidebar-nav.chat-sidebar-nav .chat-sidebar-mobile-secti /* Floating expand button positioning when classification banner is present */ body.has-classification-banner #floating-expand-btn { - top: calc(0.5rem + 40px) !important; /* Start below the classification banner */ + top: calc(0.5rem + var(--classification-banner-height)) !important; } #floating-expand-btn { @@ -245,7 +246,7 @@ body.sidebar-nav-enabled.has-classification-banner .container-fluid { } #sidebar-nav:not(.chat-sidebar-nav) .sidebar-scroll-content { - padding-bottom: 80px; + padding-bottom: max(80px, 4rem); } #conversations-section { diff --git a/application/single_app/static/css/styles.css b/application/single_app/static/css/styles.css index 397b38a8b..5bb96240a 100644 --- a/application/single_app/static/css/styles.css +++ b/application/single_app/static/css/styles.css @@ -1,5 +1,25 @@ /* css/styles.css */ +html[data-font-size="xs"] { + font-size: 75%; +} + +html[data-font-size="s"] { + font-size: 87.5%; +} + +html[data-font-size="m"] { + font-size: 100%; +} + +html[data-font-size="l"] { + font-size: 150%; +} + +html[data-font-size="xl"] { + font-size: 200%; +} + body { font-family: var(--bs-font-sans-serif); margin: 0; diff --git a/application/single_app/templates/base.html b/application/single_app/templates/base.html index 8797073ed..c0763a29f 100644 --- a/application/single_app/templates/base.html +++ b/application/single_app/templates/base.html @@ -1,7 +1,14 @@ - + +{% set allowed_font_size_preferences = ['xs', 's', 'm', 'l', 'xl'] %} +{% set saved_font_size_preference = user_settings.get('settings', {}).get('fontSizePreference', 'm') %} - + @@ -176,7 +183,8 @@ .editor-toolbar a.fa-question-circle:before { content: "?"; } .chat-container { - max-height: calc(100vh - var(--top-nav-height, 66px)); + max-height: 100%; + min-height: 0; display: flex; flex-direction: column; overflow: hidden; @@ -256,7 +264,7 @@ {% if app_settings.classification_banner_enabled and app_settings.classification_banner_text %}
+ style="background: {{ app_settings.classification_banner_color or '#ffc107' }}; color: {{ app_settings.classification_banner_text_color or '#ffffff' }}; display: flex; align-items: center; justify-content: center; text-align: center; font-weight: bold; padding: 0; height: var(--classification-banner-height); font-size: 1.1em; letter-spacing: 0.5px; position: fixed; top: 0; left: 0; width: 100%; z-index: 1051;"> {{ app_settings.classification_banner_text }}
{% endif %} diff --git a/application/single_app/templates/chats.html b/application/single_app/templates/chats.html index 460a49b16..2970e2d63 100644 --- a/application/single_app/templates/chats.html +++ b/application/single_app/templates/chats.html @@ -235,7 +235,7 @@ {% endblock %} {% block content %} -
+
diff --git a/application/single_app/templates/profile.html b/application/single_app/templates/profile.html index c73585d28..7b7871d45 100644 --- a/application/single_app/templates/profile.html +++ b/application/single_app/templates/profile.html @@ -151,6 +151,21 @@ min-height: 1.5rem; } + .font-size-options { + display: flex; + flex-wrap: wrap; + gap: 0.5rem; + } + + .font-size-options .btn { + min-width: 5.5rem; + } + + .font-size-preview { + border-left: 0.25rem solid var(--bs-primary); + font-size: 1rem; + } + .fact-memory-summary-card { background: linear-gradient(135deg, rgba(13, 110, 253, 0.08), rgba(13, 202, 240, 0.12)); border: 1px solid rgba(13, 110, 253, 0.14); @@ -742,6 +757,73 @@
Account Information
aria-labelledby="profile-settings-tab" > + {% set font_size_preference = user_settings.get('settings', {}).get('fontSizePreference', 'm') %} + {% if font_size_preference not in ['xs', 's', 'm', 'l', 'xl'] %} + {% set font_size_preference = 'm' %} + {% endif %} +
+
+
+ +
+
Appearance Preferences
+

Choose the text size used across SimpleChat.

+

+ Selecting a size previews it immediately. Choose Save to use it on future page loads. +

+
+
+
+ +
+
+
+ Font size +
+ {% for value, label, percentage in [ + ('xs', 'XS', '75%'), + ('s', 'S', '87.5%'), + ('m', 'M', '100%'), + ('l', 'L', '150%'), + ('xl', 'XL', '200%') + ] %} + + + {% endfor %} +
+
+
+ Preview: SimpleChat text will appear at this size. +
+
+
+ +
+
+ +
+
+ {% set sidebar_toggle_style = user_settings.get('settings', {}).get('sidebarToggleStyle', 'large') %} {% set latest_features_hidden_version = user_settings.get('settings', {}).get('latestFeaturesHiddenVersion') %} {% set latest_features_hidden_for_current_version = latest_features_hidden_version == config['VERSION'] %} @@ -2100,6 +2182,10 @@