Add XML structure preview tree for AI page
CI / python (push) Has been cancelled
CI / rust (push) Has been cancelled

This commit is contained in:
2026-05-22 15:40:05 +03:00
parent c9f3c12c3f
commit 7501111d29
4 changed files with 43 additions and 0 deletions
@@ -186,6 +186,7 @@ def render_html5_ai_structure_path_check(result: dict | None) -> str:
title = title_map.get(status, "Проверка пути") title = title_map.get(status, "Проверка пути")
message = str(result.get("message") or "") message = str(result.get("message") or "")
details = list(result.get("details") or []) details = list(result.get("details") or [])
preview_tree = list(result.get("preview_tree") or [])
return f""" return f"""
<section class="ai-structure-result" data-html5-ai-structure-status="{escape(status)}"> <section class="ai-structure-result" data-html5-ai-structure-status="{escape(status)}">
<div class="access-plan-head"> <div class="access-plan-head">
@@ -196,6 +197,7 @@ def render_html5_ai_structure_path_check(result: dict | None) -> str:
<li>{escape(message)}</li> <li>{escape(message)}</li>
{''.join(f'<li>{escape(str(item))}</li>' for item in details)} {''.join(f'<li>{escape(str(item))}</li>' for item in details)}
</ul> </ul>
{_render_ai_structure_preview_tree(preview_tree)}
</section> </section>
""" """
@@ -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"<li>{escape(value)}</li>" for value in values) or "<li>нет</li>"
cards.append(
f"""
<article class="access-card ai-structure-tree-card">
<strong>{escape(label)}</strong>
<ul class="ai-structure-tree-list">{body}</ul>
</article>
"""
)
if not cards:
return ""
return f"""
<div class="panel-title">Предпросмотр структуры</div>
<div class="ai-structure-hint-grid ai-structure-tree-grid">
{''.join(cards)}
</div>
"""
def _status_text(value: object) -> str: def _status_text(value: object) -> str:
mapping = { mapping = {
"ready": "готово", "ready": "готово",
@@ -468,10 +468,17 @@ def _inspect_ai_structure_input(
warnings.append("Папка `Конфигурация` не найдена. Сервер все равно попытается собрать проект по имеющимся XML/MDO/BSL.") warnings.append("Папка `Конфигурация` не найдена. Сервер все равно попытается собрать проект по имеющимся XML/MDO/BSL.")
if binaries: if binaries:
warnings.append("Во входной папке есть и бинарные .cf/.cfe, и XML-выгрузка. Для server-side подготовки будут использованы XML/MDO/BSL-файлы.") 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 { return {
"status": "ok", "status": "ok",
"message": "Сервер видит выгрузку 1С и может готовить пакет для Codex без Windows Agent.", "message": "Сервер видит выгрузку 1С и может готовить пакет для Codex без Windows Agent.",
"details": details + warnings, "details": details + warnings,
"preview_tree": preview_tree,
} }
if binaries: if binaries:
return { return {
@@ -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} .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-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-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)}} .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){.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-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: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-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-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-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-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){.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){.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}} @media(max-width:980px){.setup-layout{grid-template-columns:1fr}.setup-metrics{grid-template-columns:1fr 1fr}.settings-form{grid-template-columns:1fr}}
+3
View File
@@ -2376,6 +2376,9 @@ def test_html5_ai_structure_check_path_reports_xml_export_layout(tmp_path: Path)
assert "Главная папка: Конфигурация" in checked.text assert "Главная папка: Конфигурация" in checked.text
assert "Папки расширений: CRM" in checked.text assert "Папки расширений: CRM" in checked.text
assert "Первые файлы объектов: Конфигурация/metadata.xml, CRM/РасширениеCRM.mdo" 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): def test_html5_ai_structure_check_path_rejects_binary_input_for_xml_flow(tmp_path: Path):