Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
b7ecac6
feat: show search bar in header
nikochiko Jun 26, 2025
d13f753
feat: add mobile friendly search
nikochiko Jun 26, 2025
018477c
feat: add redirect from saved tab to go to /explore?workspace=...
nikochiko Jun 30, 2025
49f14b6
refactor: keep only one search bar component across mobile & desktop
nikochiko Jun 30, 2025
ce2e0d4
refactor: make `SearchFilters` a top-level import in routers/root.py
nikochiko Jun 30, 2025
1cbd395
fix: header wraps into multiple lines due to long workspace name
nikochiko Jun 30, 2025
5d8b348
fix: preserve old saved tab on /account/saved
nikochiko Jul 1, 2025
e5cca39
feat: change "manage workspace" link to go to members tab
nikochiko Jul 1, 2025
9abfea3
feat: add placeholder for search bar to reflect search filters
nikochiko Jul 1, 2025
93024b0
feat: show only google icon on mobile for sign in
nikochiko Jul 3, 2025
c0964e3
fix: base.py - remove unused var state & trailing space
nikochiko Jul 7, 2025
6aa3997
feat: hide explore heading during mobile search
nikochiko Jul 7, 2025
fd49fe6
fix: add cancel button on mobile search (vs hiding it on blur)
nikochiko Jul 7, 2025
9dacc49
feat: add meta tags for search pages
nikochiko Jul 9, 2025
61fac45
refactor: make function for `render_link_in_dropdown`
nikochiko Jul 10, 2025
b66b13a
fix: add "Saved" link to header
nikochiko Jul 10, 2025
b29b560
fix: breakpoint change from lg to xl for dropdown for anonymous
nikochiko Jul 10, 2025
9a89545
fix: google button doesn't show up in mobile view
nikochiko Jul 10, 2025
5000dba
refactor: add SortOptions and model for query param
nikochiko Jul 15, 2025
557ac4c
feat: make sort ui responsive
nikochiko Jul 17, 2025
b64af1a
fix: reorder workspace filter and make it wider on mobile
nikochiko Jul 17, 2025
24ea306
feat: move explore-page search bar under header
nikochiko Jul 23, 2025
7d229e3
fix: logo has display: none after navigating back from searchflow
nikochiko Jul 23, 2025
934d608
fix: set fixed height for all header navbar elements
nikochiko Jul 23, 2025
318c25c
fix: prevent losing focus on search bar after first search
nikochiko Jul 24, 2025
f166056
refactor: change type signature of _render_search_bar_with_redirect
nikochiko Jul 24, 2025
b1e5162
fix: search sort to not override order_byt
nikochiko Jul 28, 2025
5d5e36d
feat: always show run count when sorting by most runs
nikochiko Jul 28, 2025
8af8d75
fix: google one-tap button height and display
nikochiko Jul 28, 2025
f47dc37
fix: turn off keyboard suggestions on search
nikochiko Jul 29, 2025
d5fe760
refactor: move _render_search_bar_with_redirect from root to workflow…
devxpy Jul 31, 2025
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
2 changes: 1 addition & 1 deletion daras_ai_v2/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -398,7 +398,7 @@ def render(self):
& button {
font-size: 0.9rem;
padding: 0.3rem !important
}
}
}
& .nav-item {
font-size: smaller;
Expand Down
5 changes: 5 additions & 0 deletions daras_ai_v2/icons.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,10 @@
fork = '<i class="fa-regular fa-code-fork"></i>'
delete = '<i class="fa-solid fa-trash-can"></i>'
time = '<i class="fa-regular fa-clock"></i>'
calendar = '<i class="fa-regular fa-calendar"></i>'
email = '<i class="fa-solid fa-envelope"></i>'
send = '<i class="fa-regular fa-paper-plane"></i>'
sign_in = '<i class="fa-regular fa-circle-user"></i>'
sign_out = '<i class="fa-regular fa-sign-out"></i>'
remove_user = '<i class="fa-solid fa-user-minus"></i>'
add_user = '<i class="fa-solid fa-user-plus"></i>'
Expand All @@ -45,8 +47,11 @@
sparkles = '<i class="fa-solid fa-sparkles"></i>'
phone = '<i class="fa-solid fa-phone"></i>'
stars = '<i class="fa-solid fa-stars"></i>'
star = '<i class="fa-solid fa-star"></i>'
filter = '<i class="fa-solid fa-bars-filter"></i>'
sort = '<i class="fa-solid fa-sort"></i>'
info = "<i class='fa-regular fa-circle-info'></i>"
search = '<i class="fa-solid fa-magnifying-glass"></i>'

# brands
github = '<i class="fa-brands fa-github"></i>'
Expand Down
4 changes: 2 additions & 2 deletions daras_ai_v2/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
from sentry_sdk.integrations.threading import ThreadingIntegration
from starlette.templating import Jinja2Templates


# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent

Expand Down Expand Up @@ -299,14 +300,13 @@
CONTACT_URL = config("CONTACT_URL", "https://www.help.gooey.ai/contact")

HEADER_LINKS = [
("/explore/", "Explore"),
(DOCS_URL, "Docs"),
("/api/", "API"),
(BLOG_URL, "Blog"),
("/pricing", "Pricing"),
(CONTACT_URL, "Contact"),
]
HEADER_ICONS = {"/explore/": '<i class="fa-solid fa-magnifying-glass"></i>'}
HEADER_ICONS = {}

GPU_SERVER_1 = furl(config("GPU_SERVER_1", "http://gpu-1.gooey.ai"))

Expand Down
21 changes: 18 additions & 3 deletions routers/account.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
from bots.models import PublishedRun, WorkflowAccessLevel, Workflow
from daras_ai_v2 import icons, paypal
from daras_ai_v2.billing import billing_page
from daras_ai_v2.fastapi_tricks import get_route_path
from daras_ai_v2.fastapi_tricks import get_app_route_url, get_route_path
from daras_ai_v2.grid_layout_widget import grid_layout
from daras_ai_v2.manage_api_keys_widget import manage_api_keys
from daras_ai_v2.meta_content import raw_build_meta_tags
Expand Down Expand Up @@ -137,8 +137,23 @@ def profile_route(request: Request):


@gui.route(app, "/saved")
def old_saved_route(request: Request):
raise gui.RedirectException(get_route_path(saved_route))
def explore_in_current_workspace(request: Request):
from widgets.workflow_search import SearchFilters, get_filter_value_from_workspace

if not request.user or request.user.is_anonymous:
next_url = request.query_params.get("next", "/account/")
redirect_url = furl("/login", query_params={"next": next_url})
raise gui.RedirectException(str(redirect_url))

current_workspace = get_current_workspace(request.user, request.session)
search_filters = SearchFilters(
workspace=get_filter_value_from_workspace(current_workspace)
)
raise gui.RedirectException(
get_app_route_url(
explore_page, query_params=search_filters.model_dump(exclude_defaults=True)
)
)


@gui.route(app, "/account/saved")
Expand Down
167 changes: 110 additions & 57 deletions routers/root.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

import gooey_gui as gui
import sentry_sdk
from fastapi import Depends, HTTPException
from fastapi import Depends, HTTPException, Query
from fastapi.responses import RedirectResponse
from firebase_admin import auth, exceptions
from furl import furl
Expand Down Expand Up @@ -45,7 +45,8 @@
from handles.models import Handle
from routers.custom_api_router import CustomAPIRouter
from routers.static_pages import serve_static_file
from workspaces.widgets import global_workspace_selector
from widgets.workflow_search import SearchFilters, render_search_bar_with_redirect
from workspaces.widgets import global_workspace_selector, workspace_selector_link

app = CustomAPIRouter()

Expand Down Expand Up @@ -245,26 +246,16 @@ def component_page(request: Request):

@gui.route(app, "/explore/")
def explore_page(
request: Request,
search: str | None = None,
workspace: str | None = None,
workflow: str | None = None,
request: Request, search_filters: typing.Annotated[SearchFilters, Query()]
):
from widgets import explore
from widgets.workflow_search import SearchFilters

search_filters = SearchFilters(
search=search, workspace=workspace, workflow=workflow
)

with page_wrapper(request):
explore.render(request.user, search_filters)
with page_wrapper(request, search_filters=search_filters, show_search_bar=False):
explore.render(request, search_filters)

return {
"meta": raw_build_meta_tags(
url=get_og_url_path(request),
title=explore.META_TITLE,
description=explore.META_DESCRIPTION,
"meta": explore.build_meta_tags(
url=get_og_url_path(request), search_filters=search_filters
),
}

Expand Down Expand Up @@ -703,11 +694,15 @@ def get_og_url_path(request) -> str:


@contextmanager
def page_wrapper(request: Request, className=""):
context = {
"request": request,
"block_incognito": True,
}
def page_wrapper(
request: Request,
className="",
search_filters: typing.Optional[SearchFilters] = None,
show_search_bar: bool = True,
):
from routers.account import explore_in_current_workspace

context = {"request": request, "block_incognito": True}

with gui.div(className="d-flex flex-column min-vh-100"):
gui.html(templates.get_template("gtag.html").render(**context))
Expand All @@ -716,8 +711,14 @@ def page_wrapper(request: Request, className=""):
gui.div(className="header"),
gui.div(className="navbar navbar-expand-xl bg-transparent p-0 m-0"),
gui.div(className="container-xxl my-2"),
gui.div(
className="position-relative w-100 d-flex justify-content-between gap-2"
),
):
with gui.tag("a", href="/"):
with (
gui.div(className="d-md-block"),
gui.tag("a", href="/"),
):
gui.tag(
"img",
src=settings.GOOEY_LOGO_IMG,
Expand All @@ -732,17 +733,32 @@ def page_wrapper(request: Request, className=""):
height="40",
className="img-fluid logo d-sm-none",
)

if show_search_bar:
_render_mobile_search_button(request, search_filters)

with gui.div(
className="mt-2 gap-2 d-flex flex-grow-1 justify-content-end flex-wrap align-items-center"
className="d-flex gap-2 justify-content-end flex-wrap align-items-center"
):
if not show_search_bar:
render_header_link(
url=get_route_path(explore_page),
label="Explore",
icon=icons.search,
)

for url, label in settings.HEADER_LINKS:
with gui.tag("a", href=url, className="pe-2 d-none d-lg-block"):
if icon := settings.HEADER_ICONS.get(url):
with gui.div(className="d-inline-block me-2 small"):
gui.html(icon)
gui.html(label)
render_header_link(
url=url, label=label, icon=settings.HEADER_ICONS.get(url)
)

if request.user and not request.user.is_anonymous:
render_header_link(
url=get_route_path(explore_in_current_workspace),
label="Saved",
icon=icons.save,
)

current_workspace = global_workspace_selector(
request.user, request.session
)
Expand All @@ -759,6 +775,53 @@ def page_wrapper(request: Request, className=""):
gui.html(templates.get_template("login_scripts.html").render(**context))


def _render_mobile_search_button(request: Request, search_filters: SearchFilters):
with gui.div(
className="d-flex d-md-none flex-grow-1 justify-content-end",
):
gui.button(
icons.search,
type="tertiary",
unsafe_allow_html=True,
className="m-0",
onClick=JS_SHOW_MOBILE_SEARCH,
)

with gui.div(
className="d-md-flex flex-grow-1 justify-content-center align-items-center bg-white top-0 left-0",
style={"display": "none", "zIndex": "10"},
id="mobile_search_container",
):
render_search_bar_with_redirect(
request=request,
search_filters=search_filters or SearchFilters(),
)
gui.button(
"Cancel",
type="tertiary",
className="d-md-none fs-6 m-0 ms-1 p-1",
onClick=JS_HIDE_MOBILE_SEARCH,
)


JS_SHOW_MOBILE_SEARCH = """
event.preventDefault();
const mobileSearchContainer = document.querySelector("#mobile_search_container");
mobileSearchContainer.classList.add(
"d-flex", "position-absolute", "top-0", "start-0", "bottom-0", "end-0"
)
document.querySelector('#search_bar').focus();
"""

JS_HIDE_MOBILE_SEARCH = """
event.preventDefault();
const mobileSearchContainer = document.querySelector("#mobile_search_container");
mobileSearchContainer.classList.remove(
"d-flex", "position-absolute", "top-0", "start-0", "bottom-0", "end-0"
);
"""


def anonymous_login_container(request: Request, context: dict):
next_url = str(furl(request.url).set(origin=None))
login_url = str(furl("/login/", query_params=dict(next=next_url)))
Expand All @@ -771,7 +834,7 @@ def anonymous_login_container(request: Request, context: dict):
with popover, gui.div(className="d-flex align-items-center"):
gui.html(
templates.get_template("google_one_tap_button.html").render(**context)
+ '<i class="ps-2 fa-regular fa-chevron-down d-lg-none"></i>'
+ '<i class="ps-2 fa-regular fa-chevron-down d-xl-none"></i>'
)

with (
Expand All @@ -781,37 +844,27 @@ def anonymous_login_container(request: Request, context: dict):
style=dict(minWidth="200px"),
),
):
row_height = "2.2rem"

with gui.tag(
"a",
href=login_url,
className="text-decoration-none d-block bg-hover-light align-items-center px-3 my-1 py-1",
style=dict(height=row_height),
):
with gui.div(className="row align-items-center"):
with gui.div(className="col-2 d-flex justify-content-center"):
gui.html('<i class="fa-regular fa-circle-user"></i>')
with gui.div(className="col-10"):
gui.html("Sign In")
workspace_selector_link(url=login_url, label="Sign In", icon=icons.sign_in)

gui.html('<hr class="my-1"/>')

workspace_selector_link(
url=get_route_path(explore_page),
label="Explore",
icon=icons.search,
)
for url, label in settings.HEADER_LINKS:
with gui.tag(
"a",
href=url,
className="text-decoration-none d-block bg-hover-light align-items-center px-3 my-1 py-1",
style=dict(height=row_height),
):
col1, col2 = gui.columns(
[2, 10], responsive=False, className="row align-items-center"
)
if icon := settings.HEADER_ICONS.get(url):
with col1, gui.div(className="d-flex justify-content-center"):
gui.html(icon)
with col2:
gui.html(label)
workspace_selector_link(
url=url, label=label, icon=settings.HEADER_ICONS.get(url)
)


def render_header_link(url: str, label: str, icon: str | None = None):
with gui.tag("a", href=url, className="pe-2 d-none d-xl-block"):
if icon:
with gui.div(className="d-inline-block me-2 small"):
gui.html(icon)
gui.html(label)


class TabData(typing.NamedTuple):
Expand Down
22 changes: 19 additions & 3 deletions templates/google_one_tap_button.html
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
<span class="pe-2" style="height: 44px; width: 200px" data-replace-login-spinner>
<span id="g_id_signin"></span>
<style>
/* the google icon sometimes disappears from the button, this css hack fixes that */
#g_id_signin_mobile div:has(> svg) {
min-width: 40px;
min-height: 40px;
}
</style>

<span class="pe-2" style="min-width: 40px; max-width: 200px;" data-replace-login-spinner>
<span id="g_id_signin_desktop" class="d-none d-md-inline"></span>
<span id="g_id_signin_mobile" class="d-md-none"></span>
</span>

<script>
Expand All @@ -14,10 +23,17 @@
});
async function oneTapSignin() {
google.accounts.id.prompt();
google.accounts.id.renderButton(document.getElementById("g_id_signin"), {
google.accounts.id.renderButton(document.getElementById("g_id_signin_desktop"), {
shape: "rectangular",
width: 200,
text: "continue_with",
size: "large",
});
google.accounts.id.renderButton(document.getElementById("g_id_signin_mobile"), {
shape: "rectangular",
width: 40,
type: "icon",
size: "large",
});
}
</script>
Expand Down
Loading