From 460881428b426d57c4fd13e733933a0e3caa4185 Mon Sep 17 00:00:00 2001 From: Mikhail Date: Sat, 16 May 2026 23:50:03 +0300 Subject: [PATCH] Add HTML5 authoring change apply form --- services/api-server/src/api_server/html5.py | 72 ++++++++++++++++++++- services/api-server/src/api_server/main.py | 38 ++++++++++- services/api-server/tests/test_api.py | 25 +++++++ 3 files changed, 133 insertions(+), 2 deletions(-) diff --git a/services/api-server/src/api_server/html5.py b/services/api-server/src/api_server/html5.py index 2c439ef..e4a19c9 100644 --- a/services/api-server/src/api_server/html5.py +++ b/services/api-server/src/api_server/html5.py @@ -430,7 +430,12 @@ 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: +def render_html5_authoring_diff_result( + project_id: str, + preview: object | None = None, + error: str | None = None, + request_payload: dict | None = None, +) -> str: if preview is None and error is None: return '
' if error: @@ -451,6 +456,11 @@ def render_html5_authoring_diff_result(project_id: str, preview: object | 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 пустой

' + apply_form = ( + _authoring_apply_change_set_form(project_id, request_payload or {}, next_version_id) + if changed and next_version_id + else "" + ) return f"""
Diff preview · {'changed' if changed else 'unchanged'}
@@ -461,6 +471,39 @@ def render_html5_authoring_diff_result(project_id: str, preview: object | None =
{check_rows}
{diff_rows}
+ {apply_form} +
+ """ + + +def render_html5_authoring_apply_result(project_id: str, result: object | None = None, error: str | None = None) -> str: + if result is None and error is None: + return '
' + if error: + return f""" +
+
Apply change-set
+

{escape(error)}

+
+ """ + status = str(getattr(result, "status", "UNKNOWN")) + change_id = str(getattr(result, "change_id", "")) + version = getattr(result, "version", None) + version_id = str(getattr(version, "version_id", "")) + return f""" +
+
Apply change-set
+
+ {escape(status)} + {escape(change_id)} + {escape(version_id)} +
+

Change-set применен в workspace для проекта {escape(project_id)}.

""" @@ -1178,6 +1221,33 @@ def _authoring_rollback_form(project_id: str, change_id: str, rollback_version_i """ +def _authoring_apply_change_set_form(project_id: str, payload: dict, next_version_id: str) -> str: + return f""" +
+ + + + + + + + + + + +
+ {render_html5_authoring_apply_result(project_id)} + """ + + def _node_source_text(node: object | None) -> str: if node is None: return "// Модуль не найден в snapshot.\n// HTML5 IDE показывает серверный fallback без клиентского JS." diff --git a/services/api-server/src/api_server/main.py b/services/api-server/src/api_server/main.py index 7e3b66d..cd4b0d9 100644 --- a/services/api-server/src/api_server/main.py +++ b/services/api-server/src/api_server/main.py @@ -36,6 +36,7 @@ from neo4j import AsyncGraphDatabase from pydantic import BaseModel, Field from api_server.html5 import ( + render_html5_authoring_apply_result, render_html5_authoring_changes, render_html5_authoring_change_detail, render_html5_authoring_diff_result, @@ -1809,12 +1810,47 @@ async def html5_project_authoring_semantic_diff_preview(project_id: str, request ) try: preview = _authoring_semantic_diff_preview(project_id, payload) - html = render_html5_authoring_diff_result(project_id, preview) + html = render_html5_authoring_diff_result( + project_id, + preview, + request_payload={ + "routine_name": payload.routine_name, + "source_path": payload.source_path, + "original_text": payload.original_text, + "proposed_text": payload.proposed_text, + "task_id": payload.task_id, + "session_id": payload.session_id, + "user_id": payload.user_id, + }, + ) 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.post("/html5/projects/{project_id}/authoring/apply-change-set") +async def html5_project_authoring_apply_change_set(project_id: str, request: Request) -> Response: + form = await _html5_form_data(request) + payload = AuthoringApplyChangeSetRequest( + 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"), + expected_next_version_id=_form_value(form, "expected_next_version_id") or "", + approved_by=_form_value(form, "approved_by") or "", + approval_note=_form_value(form, "approval_note"), + ) + try: + result = await authoring_apply_change_set(project_id, payload) + html = render_html5_authoring_apply_result(project_id, result) + except HTTPException as error: + html = render_html5_authoring_apply_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 492ff7e..9d2d60a 100644 --- a/services/api-server/tests/test_api.py +++ b/services/api-server/tests/test_api.py @@ -2316,12 +2316,37 @@ def test_authoring_context_and_completion_preview(tmp_path: Path): assert html5_diff_preview.status_code == 200 assert "text/html" in html5_diff_preview.headers["content-type"] assert "data-html5-authoring-diff-result" in html5_diff_preview.text + assert "data-html5-authoring-apply-form" in html5_diff_preview.text + assert f'hx-post="/html5/projects/{project_id}/authoring/apply-change-set"' in html5_diff_preview.text + assert "data-html5-authoring-apply-result" in html5_diff_preview.text assert "Diff preview" in html5_diff_preview.text assert "Если Отказ Тогда" in html5_diff_preview.text assert "task-session" in html5_diff_preview.text assert "BLOCKED" in html5_diff_preview.text assert "