diff --git a/services/api-server/src/api_server/html5.py b/services/api-server/src/api_server/html5.py index b9f1c0a..b34d64f 100644 --- a/services/api-server/src/api_server/html5.py +++ b/services/api-server/src/api_server/html5.py @@ -284,6 +284,7 @@ def render_html5_flowchart( normalized_depth = min(max(depth, 1), 3) hx_url = _flowchart_url(project_id, focus, normalized_depth) oob_attr = ' hx-swap-oob="outerHTML"' if oob else "" + live_attr = ' sse-swap="project-flowchart"' if focus is None else "" if flowchart is None: return f"""
Карта связей

Сервер собирает граф проекта.

@@ -311,7 +313,7 @@ def render_html5_flowchart( class="flowchart-panel" data-html5-flowchart hx-get="{hx_url}" - hx-trigger="every 30s" + {live_attr} hx-swap="outerHTML" {oob_attr} > @@ -336,6 +338,7 @@ def render_html5_project_report(project_id: str, report: dict | None) -> str: data-html5-project-report hx-get="/html5/projects/{quote(project_id)}/report" hx-trigger="load" + sse-swap="project-report" hx-swap="outerHTML" >
Отчет проекта
@@ -357,7 +360,7 @@ def render_html5_project_report(project_id: str, report: dict | None) -> str: class="report-panel" data-html5-project-report hx-get="/html5/projects/{quote(project_id)}/report" - hx-trigger="every 15s" + sse-swap="project-report" hx-swap="outerHTML" >
Отчет проекта
@@ -424,9 +427,6 @@ def render_html5_object_report(
Отчет объекта
@@ -487,6 +487,7 @@ def render_html5_review( oob: bool = False, ) -> str: oob_attr = ' hx-swap-oob="outerHTML"' if oob else "" + live_attr = ' sse-swap="project-review"' if title == "Review" and not oob else "" if findings is None: return f"""
@@ -510,7 +512,7 @@ def render_html5_review( class="review-panel" data-html5-review hx-get="/html5/projects/{quote(project_id)}/review" - hx-trigger="every 20s" + {live_attr} hx-swap="outerHTML" {oob_attr} > diff --git a/services/api-server/src/api_server/main.py b/services/api-server/src/api_server/main.py index 1c71bbf..0a9f121 100644 --- a/services/api-server/src/api_server/main.py +++ b/services/api-server/src/api_server/main.py @@ -1,8 +1,9 @@ from __future__ import annotations -import json +import asyncio import base64 import hashlib +import json import os import re import shutil @@ -1694,21 +1695,33 @@ async def html5_project_editor(project_id: str, q: str = "") -> Response: @app.get("/html5/projects/{project_id}/events") async def html5_project_events(project_id: str, once: bool = False) -> StreamingResponse: - def stream_status(): + async def stream_status(): while True: try: snapshot = _project_snapshot_or_404(project_id) fragment = render_html5_status(project_id, snapshot) + report = await project_report(project_id) + findings = await get_review(project_id) + flowchart = await project_flowchart(project_id, focus=None, depth=1, limit=80) except HTTPException as error: fragment = f'project: {project_id}error: {error.detail}' + report = None + findings = None + flowchart = None yield _html5_sse_event("status", fragment) yield _html5_sse_event( "authoring-changes", render_html5_authoring_changes(project_id, _authoring_change_summaries(project_id)), ) + if report is not None: + yield _html5_sse_event("project-report", render_html5_project_report(project_id, report)) + if findings is not None: + yield _html5_sse_event("project-review", render_html5_review(project_id, findings)) + if flowchart is not None: + yield _html5_sse_event("project-flowchart", render_html5_flowchart(project_id, flowchart)) if once: break - time.sleep(5) + await asyncio.sleep(5) return StreamingResponse( stream_status(), diff --git a/services/api-server/tests/test_api.py b/services/api-server/tests/test_api.py index 21533a5..452d1b0 100644 --- a/services/api-server/tests/test_api.py +++ b/services/api-server/tests/test_api.py @@ -110,10 +110,13 @@ def test_html5_server_rendered_project_editor(tmp_path: Path): assert "data-html5-symbol-results" in editor.text assert "data-html5-symbol-detail" in editor.text assert "data-html5-flowchart" in editor.text + assert 'sse-swap="project-flowchart"' in editor.text assert f'hx-get="/html5/projects/{project_id}/flowchart?depth=1"' in editor.text assert "data-html5-project-report" in editor.text + assert 'sse-swap="project-report"' in editor.text assert f'hx-get="/html5/projects/{project_id}/report"' in editor.text assert "data-html5-review" in editor.text + assert 'sse-swap="project-review"' in editor.text assert f'hx-get="/html5/projects/{project_id}/review"' in editor.text assert "data-html5-authoring-preview" in editor.text assert "data-html5-authoring-preview-form" in editor.text @@ -147,7 +150,13 @@ def test_html5_server_rendered_project_editor(tmp_path: Path): first_chunk = next(events.iter_text()) assert "event: status" in first_chunk assert "event: authoring-changes" in first_chunk + assert "event: project-report" in first_chunk + assert "event: project-review" in first_chunk + assert "event: project-flowchart" in first_chunk assert "data-html5-authoring-changes" in first_chunk + assert "data-html5-project-report" in first_chunk + assert "data-html5-review" in first_chunk + assert "data-html5-flowchart" in first_chunk assert "data:" in first_chunk assert project_id in first_chunk @@ -477,6 +486,7 @@ def test_html5_flowchart_fragment(tmp_path: Path): assert "depth=2" in flowchart.text assert 'hx-target="[data-html5-flowchart]"' in flowchart.text assert "data-html5-flowchart-focus" in flowchart.text + assert 'sse-swap="project-flowchart"' not in flowchart.text assert 'hx-swap-oob="outerHTML"' not in flowchart.text assert "Карта связей" in flowchart.text assert "Nodes" in flowchart.text