diff --git a/src/second_brain/web/app.py b/src/second_brain/web/app.py index 350a2aa..b6eff16 100644 --- a/src/second_brain/web/app.py +++ b/src/second_brain/web/app.py @@ -141,32 +141,41 @@ async def source_detail(request: Request, source_id: int): ) +_IN_FLIGHT_STATUSES = [ + SourceStatus.PENDING, + SourceStatus.PULLED, + SourceStatus.TRANSCRIBED, +] + + +def _build_queue_context( + sess, + *, + add_flash: Optional[str] = None, + add_error: Optional[str] = None, +) -> dict: + """Context for queue.html / _queue_body.html (in-flight source list + form).""" + sources = ( + sess.query(Source) + .filter(Source.status.in_(_IN_FLIGHT_STATUSES)) + .order_by(Source.ingested_at.asc()) + .all() + ) + return { + "sources": [_source_to_dict(s) for s in sources], + "domains": DOMAINS, + "add_flash": add_flash, + "add_error": add_error, + } + + @app.get("/queue", response_class=HTMLResponse) async def queue_view(request: Request): - """Queue view: pending and in-flight sources.""" + """Queue view: pending and in-flight sources, with an add-to-queue form.""" db = get_database() - in_flight_statuses = [SourceStatus.PENDING, SourceStatus.PULLED, SourceStatus.TRANSCRIBED] with db.session() as sess: - sources = ( - sess.query(Source) - .filter(Source.status.in_(in_flight_statuses)) - .order_by(Source.ingested_at.asc()) - .all() - ) - source_list = [_source_to_dict(s) for s in sources] - - return templates.TemplateResponse( - request, - "index.html", - { - "sources": source_list, - "domains": DOMAINS, - "statuses": [s.value for s in SourceStatus], - "selected_domain": "", - "selected_status": "", - "page_title": "Queue", - }, - ) + ctx = _build_queue_context(sess) + return templates.TemplateResponse(request, "queue.html", ctx) # --------------------------------------------------------------------------- @@ -423,12 +432,31 @@ async def sources_add( except ValueError as exc: add_error = str(exc) - with db.session() as sess: - ctx = _build_dashboard_context( - sess, add_flash=add_flash, add_error=add_error - ) + # Dispatch the response partial based on which page submitted the form. + # HTMX always sends HX-Current-URL with the request URL of the user's + # current page; we parse the path and pick the matching body partial. + # Same /sources/add endpoint, same service call — only the rendered + # region differs, so each page refreshes its own list/counts inline. + from urllib.parse import urlparse as _urlparse - return templates.TemplateResponse(request, "_dashboard_body.html", ctx) + hx_url = request.headers.get("HX-Current-URL") or request.headers.get( + "Referer", "" + ) + source_path = _urlparse(hx_url).path if hx_url else "" + + with db.session() as sess: + if source_path.rstrip("/") == "/queue": + ctx = _build_queue_context( + sess, add_flash=add_flash, add_error=add_error + ) + template_name = "_queue_body.html" + else: + ctx = _build_dashboard_context( + sess, add_flash=add_flash, add_error=add_error + ) + template_name = "_dashboard_body.html" + + return templates.TemplateResponse(request, template_name, ctx) # --------------------------------------------------------------------------- diff --git a/src/second_brain/web/templates/_add_to_queue_form.html b/src/second_brain/web/templates/_add_to_queue_form.html new file mode 100644 index 0000000..126ed3d --- /dev/null +++ b/src/second_brain/web/templates/_add_to_queue_form.html @@ -0,0 +1,67 @@ +{# Add-to-queue form. Reused by /dashboard and /queue — the only + difference between the two callers is the HTMX swap target, which the + parent template sets via `{% set swap_target = "#…-body" %}` before + including this partial. POST goes to /sources/add either way; the + route inspects HX-Current-URL to pick the right body partial in its + response. #} +
+
+

+ Add to queue +

+

video on a known host (YouTube / Vimeo) → tower; otherwise treated as an article. YouTube /playlist?list=… fans out.

+
+ + {% if add_flash %} +
+ ✓ {{ add_flash }} +
+ {% endif %} + {% if add_error %} +
+ ✗ {{ add_error }} +
+ {% endif %} + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ +
+ +
+ + +
+
+
diff --git a/src/second_brain/web/templates/_dashboard_body.html b/src/second_brain/web/templates/_dashboard_body.html index dfc1133..39b8dc1 100644 --- a/src/second_brain/web/templates/_dashboard_body.html +++ b/src/second_brain/web/templates/_dashboard_body.html @@ -18,68 +18,8 @@ } %}
- -
-
-

- Add to queue -

-

video on a known host (YouTube / Vimeo) → tower; otherwise treated as an article

-
- - {% if add_flash %} -
- ✓ {{ add_flash }} -
- {% endif %} - {% if add_error %} -
- ✗ {{ add_error }} -
- {% endif %} - -
- -
- - -
- -
- - -
- -
- - -
- -
- -
- -
- - -
-
-
+ {% set swap_target = "#dashboard-body" %} + {% include "_add_to_queue_form.html" %}
diff --git a/src/second_brain/web/templates/_queue_body.html b/src/second_brain/web/templates/_queue_body.html new file mode 100644 index 0000000..35384b2 --- /dev/null +++ b/src/second_brain/web/templates/_queue_body.html @@ -0,0 +1,62 @@ +{# HTMX-replaceable queue body. POST /sources/add re-renders this when + the form was submitted from /queue (dispatched server-side via + HX-Current-URL); hx-target="#queue-body" + hx-swap="outerHTML" keeps + the wrapping div in place for the next swap. #} +{% set status_colors = { + 'pending': 'bg-gray-100 text-gray-700', + 'pulled': 'bg-blue-100 text-blue-700', + 'transcribed': 'bg-yellow-100 text-yellow-700', + 'analyzed': 'bg-orange-100 text-orange-700', + 'accepted': 'bg-green-100 text-green-700', + 'published': 'bg-purple-100 text-purple-700', + 'failed': 'bg-red-100 text-red-700' +} %} +{% set domain_colors = { + 'development': 'bg-cyan-50 text-cyan-700 border-cyan-200', + 'content': 'bg-pink-50 text-pink-700 border-pink-200', + 'business': 'bg-amber-50 text-amber-700 border-amber-200', + 'homelab': 'bg-teal-50 text-teal-700 border-teal-200' +} %} +
+ + {% set swap_target = "#queue-body" %} + {% include "_add_to_queue_form.html" %} + + + {% if sources %} + + {% else %} +
+

Queue is empty.

+

Add a URL above to get started.

+
+ {% endif %} +
diff --git a/src/second_brain/web/templates/queue.html b/src/second_brain/web/templates/queue.html new file mode 100644 index 0000000..faa8806 --- /dev/null +++ b/src/second_brain/web/templates/queue.html @@ -0,0 +1,15 @@ +{% extends "base.html" %} + +{% block title %}Queue — second-brain{% endblock %} + +{% block content %} +
+

+ Queue + ({{ sources|length }}) +

+ pending → pulled → transcribed +
+ +{% include "_queue_body.html" %} +{% endblock %}