From 7501111d294089c2a817ce554ca9e1b602dbec85 Mon Sep 17 00:00:00 2001 From: Mikhail Date: Fri, 22 May 2026 15:40:05 +0300 Subject: [PATCH] Add XML structure preview tree for AI page --- .../src/api_server/html5_ai_structure.py | 30 +++++++++++++++++++ .../html5_ai_structure_controller.py | 7 +++++ .../src/api_server/static/html5/html5.css | 3 ++ services/api-server/tests/test_api.py | 3 ++ 4 files changed, 43 insertions(+) diff --git a/services/api-server/src/api_server/html5_ai_structure.py b/services/api-server/src/api_server/html5_ai_structure.py index c32b5b7..bb4d488 100644 --- a/services/api-server/src/api_server/html5_ai_structure.py +++ b/services/api-server/src/api_server/html5_ai_structure.py @@ -186,6 +186,7 @@ def render_html5_ai_structure_path_check(result: dict | None) -> str: title = title_map.get(status, "Проверка пути") message = str(result.get("message") or "") details = list(result.get("details") or []) + preview_tree = list(result.get("preview_tree") or []) return f"""
@@ -196,6 +197,7 @@ def render_html5_ai_structure_path_check(result: dict | None) -> str:
  • {escape(message)}
  • {''.join(f'
  • {escape(str(item))}
  • ' for item in details)} + {_render_ai_structure_preview_tree(preview_tree)}
    """ @@ -318,6 +320,34 @@ def _diagnostics(items: list[object]) -> str: """ +def _render_ai_structure_preview_tree(items: list[object]) -> str: + if not items: + return "" + cards: list[str] = [] + for item in items: + if not isinstance(item, dict): + continue + label = str(item.get("label") or "") + values = [str(value) for value in list(item.get("items") or []) if str(value).strip()] + body = "".join(f"
  • {escape(value)}
  • " for value in values) or "
  • нет
  • " + cards.append( + f""" +
    + {escape(label)} + +
    + """ + ) + if not cards: + return "" + return f""" +
    Предпросмотр структуры
    +
    + {''.join(cards)} +
    + """ + + def _status_text(value: object) -> str: mapping = { "ready": "готово", diff --git a/services/api-server/src/api_server/html5_ai_structure_controller.py b/services/api-server/src/api_server/html5_ai_structure_controller.py index 68212f7..4070488 100644 --- a/services/api-server/src/api_server/html5_ai_structure_controller.py +++ b/services/api-server/src/api_server/html5_ai_structure_controller.py @@ -468,10 +468,17 @@ def _inspect_ai_structure_input( warnings.append("Папка `Конфигурация` не найдена. Сервер все равно попытается собрать проект по имеющимся XML/MDO/BSL.") if binaries: warnings.append("Во входной папке есть и бинарные .cf/.cfe, и XML-выгрузка. Для server-side подготовки будут использованы XML/MDO/BSL-файлы.") + preview_tree = [ + {"label": "Главная конфигурация", "items": [layout["main_configuration_root"]]}, + {"label": "Папки расширений", "items": layout["extension_roots"] or ["нет"]}, + {"label": "Первые файлы объектов", "items": object_files or ["не найдены"]}, + {"label": "Первые файлы модулей", "items": module_files or ["не найдены"]}, + ] return { "status": "ok", "message": "Сервер видит выгрузку 1С и может готовить пакет для Codex без Windows Agent.", "details": details + warnings, + "preview_tree": preview_tree, } if binaries: return { diff --git a/services/api-server/src/api_server/static/html5/html5.css b/services/api-server/src/api_server/static/html5/html5.css index 96fdb82..f8e9323 100644 --- a/services/api-server/src/api_server/static/html5/html5.css +++ b/services/api-server/src/api_server/static/html5/html5.css @@ -15,14 +15,17 @@ .access-card[hx-get]{cursor:pointer}.access-card[hx-get]:hover{background:#f8fbff}.access-user-detail{border-bottom:1px solid var(--line);background:#fff} .ai-structure-form{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:10px;align-items:end;padding:12px 16px;border-bottom:1px solid var(--line);background:#fbfcfe;overflow-x:hidden}.ai-structure-form label{display:grid;gap:5px;font-size:11px;font-weight:900;color:var(--muted);text-transform:uppercase;min-width:0}.ai-structure-form input{height:32px;min-width:0;width:100%;border:1px solid var(--line);background:#fff;padding:0 8px;color:var(--text);font:13px/1.3 system-ui,-apple-system,Segoe UI,sans-serif;text-transform:none}.ai-structure-field{min-width:0}.ai-structure-field-wide{grid-column:span 2}.ai-structure-field-compact{grid-column:span 1}.ai-structure-form .checkbox-row{display:flex;align-items:center;gap:7px;height:32px}.ai-structure-form .checkbox-row input{width:16px;height:16px;flex:0 0 auto}.ai-structure-submit{width:100%;margin:0;min-width:0}.ai-structure-result{background:#fff} .ai-structure-hint{border-bottom:1px solid var(--line);background:#fff}.ai-structure-hint-grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));background:#fff}.ai-structure-hint-grid .access-card:nth-child(3n+1),.ai-structure-hint-grid .access-card:nth-child(3n+2){border-right:1px solid var(--line)}.ai-structure-hint-grid code{font-family:ui-monospace,SFMono-Regular,Consolas,monospace;font-size:12px} +.ai-structure-tree-grid{grid-template-columns:repeat(2,minmax(0,1fr));border-top:1px solid var(--line)}.ai-structure-tree-grid .access-card:nth-child(3n+1),.ai-structure-tree-grid .access-card:nth-child(3n+2){border-right:0}.ai-structure-tree-grid .access-card:nth-child(odd){border-right:1px solid var(--line)}.ai-structure-tree-card strong{margin-bottom:6px}.ai-structure-tree-list{margin:0;padding:0;list-style:none}.ai-structure-tree-list li{position:relative;padding:4px 0 4px 16px;color:var(--muted);font-size:12px;line-height:1.4}.ai-structure-tree-list li::before{content:"";position:absolute;left:4px;top:11px;width:6px;height:1px;background:#9aa4b2} .ai-structure-progress{border-bottom:1px solid var(--line);background:#fff}.ai-progress-head{display:flex;align-items:center;gap:10px;padding:10px 12px;border-bottom:1px solid var(--line);background:#fbfcfe}.ai-progress-head strong{flex:1}.ai-progress-head small{font-weight:900;color:var(--muted);font-variant-numeric:tabular-nums}.ai-progress-spinner{width:18px;height:18px;border:3px solid #c9d6ec;border-top-color:var(--brand);border-radius:50%;animation:aiProgressSpin 900ms linear infinite}.ai-progress-bar{height:6px;background:#eef2f7;overflow:hidden}.ai-progress-bar span{display:block;width:8%;height:100%;background:var(--brand);transition:width 700ms ease}.ai-progress-metrics{display:grid;grid-template-columns:repeat(3,1fr);margin:0}.ai-progress-metrics div{padding:10px 12px;border-right:1px solid var(--line);border-bottom:1px solid var(--line)}.ai-progress-metrics div:last-child{border-right:0}.ai-progress-metrics dt{font-size:11px;font-weight:900;text-transform:uppercase;color:var(--muted)}.ai-progress-metrics dd{margin:3px 0 0;font-weight:900}.ai-structure-progress[hidden]{display:none}.ai-structure-progress.htmx-request,.ai-structure-progress[data-ai-structure-progress-state="running"]{display:block}@keyframes aiProgressSpin{to{transform:rotate(360deg)}} @media(max-width:980px){.layout{grid-template-columns:1fr;height:auto}.tree,.inspector{max-height:320px}.editor{min-height:520px}.hero{display:block}.shell{padding:16px}} @media(max-width:980px){.access-layout{grid-template-columns:1fr;height:auto}.access-nav,.access-side{max-height:360px}.access-role-grid{grid-template-columns:1fr}.access-role-grid .access-card:nth-child(odd){border-right:0}} @media(max-width:980px){.access-builder-form{grid-template-columns:1fr}.access-builder-actions{justify-content:stretch}.access-builder-actions button{flex:1}} @media(max-width:1180px){.ai-structure-form{grid-template-columns:repeat(2,minmax(0,1fr))}.ai-structure-field-wide{grid-column:1/-1}} @media(max-width:1180px){.ai-structure-hint-grid{grid-template-columns:1fr 1fr}.ai-structure-hint-grid .access-card:nth-child(3n+1),.ai-structure-hint-grid .access-card:nth-child(3n+2){border-right:0}.ai-structure-hint-grid .access-card:nth-child(odd){border-right:1px solid var(--line)}} +@media(max-width:1180px){.ai-structure-tree-grid{grid-template-columns:1fr 1fr}.ai-structure-tree-grid .access-card:nth-child(odd){border-right:1px solid var(--line)}} @media(max-width:700px){.ai-structure-form{grid-template-columns:1fr}.ai-structure-field-wide,.ai-structure-field-compact,.ai-structure-submit{grid-column:1/-1}} @media(max-width:700px){.ai-structure-hint-grid{grid-template-columns:1fr}.ai-structure-hint-grid .access-card:nth-child(odd){border-right:0}} +@media(max-width:700px){.ai-structure-tree-grid{grid-template-columns:1fr}.ai-structure-tree-grid .access-card:nth-child(odd){border-right:0}} @media(max-width:980px){.ai-progress-metrics{grid-template-columns:1fr}} @media(max-width:980px){.form-designer-body{grid-template-columns:1fr}.form-property-panel{border-left:0;border-top:1px solid var(--line)}.form-field[data-html5-form-width="half"],.form-field[data-html5-form-width="third"]{grid-column:1/-1}.form-editor-row[data-html5-form-element-editor]{grid-template-columns:1fr}.property-row{grid-template-columns:1fr}} @media(max-width:980px){.setup-layout{grid-template-columns:1fr}.setup-metrics{grid-template-columns:1fr 1fr}.settings-form{grid-template-columns:1fr}} diff --git a/services/api-server/tests/test_api.py b/services/api-server/tests/test_api.py index 07b7643..57d42f7 100644 --- a/services/api-server/tests/test_api.py +++ b/services/api-server/tests/test_api.py @@ -2376,6 +2376,9 @@ def test_html5_ai_structure_check_path_reports_xml_export_layout(tmp_path: Path) assert "Главная папка: Конфигурация" in checked.text assert "Папки расширений: CRM" in checked.text assert "Первые файлы объектов: Конфигурация/metadata.xml, CRM/РасширениеCRM.mdo" in checked.text + assert "Предпросмотр структуры" in checked.text + assert "Главная конфигурация" in checked.text + assert "Первые файлы модулей" in checked.text def test_html5_ai_structure_check_path_rejects_binary_input_for_xml_flow(tmp_path: Path):