From c14db34f14e23e6f221edf6a79db44950c74ae31 Mon Sep 17 00:00:00 2001 From: Mikhail Date: Sat, 16 May 2026 23:24:42 +0300 Subject: [PATCH] Add HTML5 authoring diff preview --- services/api-server/src/api_server/html5.py | 54 +++++++++++++++++++++ services/api-server/src/api_server/main.py | 21 ++++++++ services/api-server/tests/test_api.py | 26 ++++++++++ 3 files changed, 101 insertions(+) diff --git a/services/api-server/src/api_server/html5.py b/services/api-server/src/api_server/html5.py index 20b0796..2c439ef 100644 --- a/services/api-server/src/api_server/html5.py +++ b/services/api-server/src/api_server/html5.py @@ -371,6 +371,25 @@ def render_html5_authoring_preview(project_id: str, preview: object | None, erro {render_html5_authoring_preview_result(project_id)} +
+ + + + + + + + +
+ {render_html5_authoring_diff_result(project_id)} """ return render_html5_authoring_preview_result(project_id, preview, error) @@ -411,6 +430,41 @@ def render_html5_authoring_preview_result(project_id: str, preview: object | Non """ +def render_html5_authoring_diff_result(project_id: str, preview: object | None = None, error: str | None = None) -> str: + if preview is None and error is None: + return '
' + if error: + return f""" +
+
Diff preview
+

{escape(error)}

+
+ """ + changed = bool(getattr(preview, "changed", False)) + added = getattr(preview, "added_lines", 0) + removed = getattr(preview, "removed_lines", 0) + target = getattr(preview, "target", None) + target_name = getattr(target, "qualified_name", None) or getattr(target, "name", None) or "target unavailable" + checks = getattr(preview, "checks", []) or [] + diff = getattr(preview, "semantic_diff", []) or [] + version_preview = getattr(preview, "version_preview", None) + next_version_id = str(getattr(version_preview, "next_version_id", "")) + check_rows = "".join(_authoring_check_item(check) for check in checks[:8]) + diff_rows = "".join(_authoring_diff_item(line) for line in diff[:12]) or '

Diff пустой

' + return f""" +
+
Diff preview · {'changed' if changed else 'unchanged'}
+
+ {escape(str(target_name))} + +{escape(str(added))} / -{escape(str(removed))} + {escape(next_version_id or "version preview unavailable")} +
+
{check_rows}
+
{diff_rows}
+
+ """ + + def render_html5_authoring_change_detail(project_id: str, preview: object | None) -> str: if preview is None: return f""" diff --git a/services/api-server/src/api_server/main.py b/services/api-server/src/api_server/main.py index f1d9d87..7e3b66d 100644 --- a/services/api-server/src/api_server/main.py +++ b/services/api-server/src/api_server/main.py @@ -38,6 +38,7 @@ from pydantic import BaseModel, Field from api_server.html5 import ( render_html5_authoring_changes, render_html5_authoring_change_detail, + render_html5_authoring_diff_result, render_html5_authoring_preview_result, render_html5_authoring_rollback_result, render_html5_editor, @@ -1794,6 +1795,26 @@ async def html5_project_authoring_completion_preview(project_id: str, request: R return Response(html, media_type="text/html; charset=utf-8") +@app.post("/html5/projects/{project_id}/authoring/semantic-diff-preview") +async def html5_project_authoring_semantic_diff_preview(project_id: str, request: Request) -> Response: + form = await _html5_form_data(request) + payload = AuthoringSemanticDiffPreviewRequest( + routine_name=_form_value(form, "routine_name"), + source_path=_form_value(form, "source_path"), + original_text=_form_value(form, "original_text") or "", + proposed_text=_form_value(form, "proposed_text") or "", + task_id=_form_value(form, "task_id"), + session_id=_form_value(form, "session_id"), + user_id=_form_value(form, "user_id"), + ) + try: + preview = _authoring_semantic_diff_preview(project_id, payload) + html = render_html5_authoring_diff_result(project_id, preview) + except HTTPException as error: + html = render_html5_authoring_diff_result(project_id, error=str(error.detail)) + return Response(html, media_type="text/html; charset=utf-8") + + @app.get("/html5/projects/{project_id}/setup") async def html5_project_setup(project_id: str) -> Response: setup = _project_setup_response(project_id) diff --git a/services/api-server/tests/test_api.py b/services/api-server/tests/test_api.py index 2540013..492ff7e 100644 --- a/services/api-server/tests/test_api.py +++ b/services/api-server/tests/test_api.py @@ -115,6 +115,8 @@ def test_html5_server_rendered_project_editor(tmp_path: Path): assert "data-html5-authoring-preview" in editor.text assert "data-html5-authoring-preview-form" in editor.text assert f'hx-post="/html5/projects/{project_id}/authoring/completion-preview"' in editor.text + assert "data-html5-authoring-diff-form" in editor.text + assert f'hx-post="/html5/projects/{project_id}/authoring/semantic-diff-preview"' in editor.text assert "data-html5-authoring-changes" in editor.text assert f'hx-get="/html5/projects/{project_id}/authoring/changes"' in editor.text assert 'hx-get="/html5/projects/' in editor.text @@ -2296,6 +2298,30 @@ def test_authoring_context_and_completion_preview(tmp_path: Path): assert "ADD" in html5_preview.text assert "