Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 70 additions & 5 deletions src/searchapp/web/dash_app.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,33 @@
from dash import Dash, dcc, html, callback, Input, Output, State
from dash import Dash, dcc, html, callback, Input, Output, State, ClientsideFunction
import dash_bootstrap_components as dbc
from searchapp.core.inference.inference import Inference
from searchapp.core.search.web import WebSearch
from searchapp.api.controller import InputController

# Initialize with both themes available
app = Dash(
__name__,
external_stylesheets=[dbc.themes.MINTY],
external_stylesheets=[dbc.themes.MINTY, dbc.themes.DARKLY],
meta_tags=[{"name": "viewport", "content": "width=device-width, initial-scale=1"}],
)

# Create theme toggle button
theme_toggle = dbc.Button(
"Toggle Theme",
id="theme-toggle",
color="secondary",
size="sm",
className="position-fixed top-0 end-0 m-2",
style={"zIndex": 1000},
)

# Store component for theme state
theme_store = dcc.Store(id="theme-store", storage_type="local")

app.layout = dbc.Container(
[
theme_store, # Add theme store
theme_toggle, # Add theme toggle button
dbc.Row(
dbc.Col(
html.Div(
Expand Down Expand Up @@ -72,7 +88,11 @@
id="search-button",
color="primary",
outline=True,
style={"width": "100%"},
style={
"width": "100%",
"border-color": "var(--bs-primary)",
"color": "var(--bs-primary)",
},
),
className="mb-4",
)
Expand All @@ -89,13 +109,14 @@
className="markdown-container invisible",
style={
"whiteSpace": "pre-wrap", # Preserves newlines and spaces
"background-color": "#434343",
"padding": "20px",
"border-radius": "5px",
"overflow-wrap": "break-word", # Ensures long words break
"word-wrap": "break-word", # Fallback for older browsers
"word-break": "break-word", # Another fallback
"max-width": "100%", # Ensures markdown doesn't exceed container
"color": "var(--bs-body-color)", # Use Bootstrap's theme-aware color
"background-color": "var(--bs-secondary-bg)", # Use Bootstrap's theme-aware background
},
),
style={
Expand Down Expand Up @@ -166,5 +187,49 @@ def update_search_formatted(n_clicks, search_input):

return "No search performed yet."

# Theme switching callbacks
@callback(
Output("theme-store", "data"),
Input("theme-toggle", "n_clicks"),
State("theme-store", "data"),
)
def toggle_theme(n_clicks, current_theme):
if n_clicks is None:
# Initial load - set default theme
return {"theme": "light"}

# Toggle between light and dark
if current_theme is None or current_theme.get("theme") == "light":
return {"theme": "dark"}
return {"theme": "light"}

# Update theme CSS
app.clientside_callback(
"""
function(theme_data) {
if (!theme_data) return;

const theme = theme_data.theme;
const lightTheme = document.querySelector('link[href*="minty"]');
const darkTheme = document.querySelector('link[href*="darkly"]');

if (theme === 'dark') {
lightTheme.disabled = true;
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm getting errors in dash when running this locally:

null is not an object (evaluating 'lightTheme.disabled = true')
(This error originated from the built-in JavaScript code that runs Dash apps. Click to see the full stack trace or open your browser's console.)
@http://192.168.1.194:8050/:48:23

_callee2$@http://192.168.1.194:8050/_dash-component-suites/dash/dash-renderer/build/dash_renderer.v2_18_2m1734075790.dev.js:604:77

tryCatch@http://192.168.1.194:8050/_dash-component-suites/dash/dash-renderer/build/dash_renderer.v2_18_2m1734075790.dev.js:424:1066

@http://192.168.1.194:8050/_dash-component-suites/dash/dash-renderer/build/dash_renderer.v2_18_2m1734075790.dev.js:424:3014

asyncGeneratorStep@http://192.168.1.194:8050/_dash-component-suites/dash/dash-renderer/build/dash_renderer.v2_18_2m1734075790.dev.js:430:70

_next@http://192.168.1.194:8050/_dash-component-suites/dash/dash-renderer/build/dash_renderer.v2_18_2m1734075790.dev.js:431:181

@http://192.168.1.194:8050/_dash-component-suites/dash/dash-renderer/build/dash_renderer.v2_18_2m1734075790.dev.js:431:304

Promise@[native code]

@http://192.168.1.194:8050/_dash-component-suites/dash/dash-renderer/build/dash_renderer.v2_18_2m1734075790.dev.js:431:101

tryCatch@http://192.168.1.194:8050/_dash-component-suites/dash/dash-renderer/build/dash_renderer.v2_18_2m1734075790.dev.js:424:1066

@http://192.168.1.194:8050/_dash-component-suites/dash/dash-renderer/build/dash_renderer.v2_18_2m1734075790.dev.js:424:3014

asyncGeneratorStep@http://192.168.1.194:8050/_dash-component-suites/dash/dash-renderer/build/dash_renderer.v2_18_2m1734075790.dev.js:430:70

_next@http://192.168.1.194:8050/_dash-component-suites/dash/dash-renderer/build/dash_renderer.v2_18_2m1734075790.dev.js:431:181

@http://192.168.1.194:8050/_dash-component-suites/dash/dash-renderer/build/dash_renderer.v2_18_2m1734075790.dev.js:431:304

Promise@[native code]

@http://192.168.1.194:8050/_dash-component-suites/dash/dash-renderer/build/dash_renderer.v2_18_2m1734075790.dev.js:431:101

executeCallback@http://192.168.1.194:8050/_dash-component-suites/dash/dash-renderer/build/dash_renderer.v2_18_2m1734075790.dev.js:1231:34

_map@http://192.168.1.194:8050/_dash-component-suites/dash/dash-renderer/build/dash_renderer.v2_18_2m1734075790.dev.js:50263:21

_callee2$@http://192.168.1.194:8050/_dash-component-suites/dash/dash-renderer/build/dash_renderer.v2_18_2m1734075790.dev.js:2545:320

tryCatch@http://192.168.1.194:8050/_dash-component-suites/dash/dash-renderer/build/dash_renderer.v2_18_2m1734075790.dev.js:2462:1066

@http://192.168.1.194:8050/_dash-component-suites/dash/dash-renderer/build/dash_renderer.v2_18_2m1734075790.dev.js:2462:3014

asyncGeneratorStep@http://192.168.1.194:8050/_dash-component-suites/dash/dash-renderer/build/dash_renderer.v2_18_2m1734075790.dev.js:2472:70

_next@http://192.168.1.194:8050/_dash-component-suites/dash/dash-renderer/build/dash_renderer.v2_18_2m1734075790.dev.js:2473:181

@http://192.168.1.194:8050/_dash-component-suites/dash/dash-renderer/build/dash_renderer.v2_18_2m1734075790.dev.js:2473:304

Promise@[native code]

@http://192.168.1.194:8050/_dash-component-suites/dash/dash-renderer/build/dash_renderer.v2_18_2m1734075790.dev.js:2473:101

@http://192.168.1.194:8050/_dash-component-suites/dash/dash-renderer/build/dash_renderer.v2_18_2m1734075790.dev.js:357:17

forEach@[native code]

@http://192.168.1.194:8050/_dash-component-suites/dash/dash-renderer/build/dash_renderer.v2_18_2m1734075790.dev.js:355:22

dispatch@http://192.168.1.194:8050/_dash-component-suites/dash/dash-renderer/build/dash_renderer.v2_18_2m1734075790.dev.js:44748:15

_callee$@http://192.168.1.194:8050/_dash-component-suites/dash/dash-renderer/build/dash_renderer.v2_18_2m1734075790.dev.js:2870:21

tryCatch@http://192.168.1.194:8050/_dash-component-suites/dash/dash-renderer/build/dash_renderer.v2_18_2m1734075790.dev.js:2637:1066

@http://192.168.1.194:8050/_dash-component-suites/dash/dash-renderer/build/dash_renderer.v2_18_2m1734075790.dev.js:2637:3014

asyncGeneratorStep@http://192.168.1.194:8050/_dash-component-suites/dash/dash-renderer/build/dash_renderer.v2_18_2m1734075790.dev.js:2643:70

_next@http://192.168.1.194:8050/_dash-component-suites/dash/dash-renderer/build/dash_renderer.v2_18_2m1734075790.dev.js:2644:181

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@devloai do you see what could be causing the problem here?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking into the theme switching error - seems like we have a null reference issue with the theme object. Will investigate and push a fix shortly.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @DanH-Walpole! I've identified and fixed the issue you're experiencing. The problem was related to case sensitivity in the theme stylesheet selectors.

The error null is not an object (evaluating 'lightTheme.disabled = true') occurred because the JavaScript code was trying to find stylesheets using uppercase theme names (MINTY and DARKLY), but the actual stylesheet URLs contain lowercase names.

I've made the following fix:

  • Updated the theme selectors in the clientside callback to use lowercase:
    • Changed 'link[href*="MINTY"]' to 'link[href*="minty"]'
    • Changed 'link[href*="DARKLY"]' to 'link[href*="darkly"]'

The changes have been committed and pushed to the branch. All tests are passing, and the theme toggle should now work correctly. Could you please verify that the error is resolved on your local environment?

darkTheme.disabled = false;
document.body.style.backgroundColor = '#1a1a1a';
document.body.style.color = '#ffffff';
} else {
lightTheme.disabled = false;
darkTheme.disabled = true;
document.body.style.backgroundColor = '#ffffff';
document.body.style.color = '#000000';
}
return '';
}
""",
Output("theme-toggle", "style"),
Input("theme-store", "data"),
)

if __name__ == "__main__":
app.run(debug=True, host="0.0.0.0")
app.run(debug=True, host="0.0.0.0")
22 changes: 22 additions & 0 deletions tests/test_dark_mode.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import pytest
from src.searchapp.web.dash_app import toggle_theme

def test_toggle_theme_initial():
"""Test initial theme state"""
result = toggle_theme(None, None)
assert result == {"theme": "light"}, "Initial theme should be light"

def test_toggle_theme_to_dark():
"""Test toggling from light to dark theme"""
result = toggle_theme(1, {"theme": "light"})
assert result == {"theme": "dark"}, "Theme should switch to dark"

def test_toggle_theme_to_light():
"""Test toggling from dark to light theme"""
result = toggle_theme(1, {"theme": "dark"})
assert result == {"theme": "light"}, "Theme should switch to light"

def test_toggle_theme_with_invalid_state():
"""Test toggling with invalid/missing state"""
result = toggle_theme(1, None)
assert result == {"theme": "dark"}, "Invalid state should default to dark"