From d8394e4e8916e26e058c2dbb1043f63d539c877e Mon Sep 17 00:00:00 2001 From: Mikhail Date: Fri, 22 May 2026 15:02:26 +0300 Subject: [PATCH] Clarify XML AI structure flow in HTML5 UI --- .../src/api_server/html5_ai_structure.py | 81 +++++++++++++++++++ .../src/api_server/static/html5/html5.css | 3 + services/api-server/tests/test_api.py | 3 + 3 files changed, 87 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 15e8d0a..50c8c9e 100644 --- a/services/api-server/src/api_server/html5_ai_structure.py +++ b/services/api-server/src/api_server/html5_ai_structure.py @@ -32,6 +32,7 @@ def render_html5_ai_structure_page(
Подготовка структуры
{render_html5_ai_structure_agent_panel(project_id, agent_info=agent_info)} + {render_html5_ai_structure_source_hint()} {render_html5_ai_structure_form(project_id, saved_credentials=saved_credentials)}
{render_html5_ai_structure_path_check(None)}

Пути должны быть доступны серверу SFERA/API. Для docker-test используйте папку, смонтированную или доступную внутри контейнера; локальные диски Windows и закрытые SMB-папки без учетных данных сервер не увидит.

@@ -77,6 +78,35 @@ def render_html5_ai_structure_agent_panel(project_id: str, *, agent_info: dict[s """ +def render_html5_ai_structure_source_hint() -> str: + return """ +
+
+ xml/xdt + Ожидаемый формат выгрузки 1С +
+
+
+ Основная конфигурация + Во входной папке должна быть папка Конфигурация с XML/BSL/MDO-файлами выгрузки 1С. +
+
+ Расширения + Каждое расширение кладите в отдельную соседнюю папку с его именем. SFERA загрузит их как части одного проекта. +
+
+ Без лишнего сырья + Для Codex будет собран compact-пакет: краткие индексы, brief-контекст, layout проекта и выборочные исходники вместо полного дубля всей выгрузки. +
+
+
    +
  • Если на входе уже XML-выгрузка, Windows Agent не нужен: сервер сам строит NormalizedProject, SIR и пакет для Codex.
  • +
  • Agent нужен только для бинарных .cf/.cfe, когда сначала надо получить XML-структуру из платформы 1С.
  • +
+
+ """ + + def render_html5_ai_structure_form(project_id: str, *, saved_credentials: dict[str, str] | None = None) -> str: saved_credentials = saved_credentials or {} saved_username = str(saved_credentials.get("username") or "") @@ -178,6 +208,7 @@ def render_html5_ai_structure_result(result: dict | None) -> str: artifacts = list(result.get("artifacts") or []) snapshot = result.get("snapshot") or {} normalized = result.get("normalized") or {} + source_layout = result.get("source_layout") or {} status = _status_text(result.get("status")) return f"""
@@ -192,6 +223,7 @@ def render_html5_ai_structure_result(result: dict | None) -> str:
Связи
{escape(str(snapshot.get("edges", 0)))}
Объекты
{escape(str(normalized.get("objects", 0)))}
+ {render_html5_ai_structure_result_summary(source_layout, normalized)}
Артефакты
{''.join(f'
{escape(_artifact_text(item))}Файл пакета структуры
' for item in artifacts)}
{_diagnostics(diagnostics)} @@ -242,6 +274,42 @@ def render_html5_ai_structure_error(message: str) -> str: """ +def render_html5_ai_structure_result_summary(source_layout: dict[str, object], normalized: dict[str, object]) -> str: + layout_kind = str(source_layout.get("kind") or "unknown") + main_root = str(source_layout.get("main_configuration_root") or "не определена") + extension_roots = [str(item) for item in list(source_layout.get("extension_roots") or []) if str(item).strip()] + extensions_count = escape(str(normalized.get("extensions", len(extension_roots) or 0))) + return f""" +
Структура проекта для Codex
+
+
+ Тип раскладки + {escape(_layout_kind_text(layout_kind))} +
+
+ Главная папка + {escape(main_root)} +
+
+ Расширений + {extensions_count} +
+
+ Первый вход для Codex + context/project-brief.md, indexes/project-layout.json, indexes/objects-compact.json, indexes/modules-compact.json +
+
+ Исходники + В source/ попадает выборочная UTF-8-копия BSL/MDO и ключевых XML, а не полный дубль выгрузки. +
+
+ Папки расширений + {escape(", ".join(extension_roots) or "нет")} +
+
+ """ + + def _diagnostics(items: list[object]) -> str: if not items: return "" @@ -267,6 +335,9 @@ def _artifact_text(value: object) -> str: "ai_context.md": "Контекст для ИИ", "export_plan.md": "План выгрузки", "codex_package": "Папка для Codex", + "project_layout.json": "Карта раскладки проекта", + "compact_objects.json": "Компактный индекс объектов", + "compact_modules.json": "Компактный индекс модулей", "sir_snapshot.json": "Снимок графа SIR", "ai_objects.json": "Индекс объектов", "ai_modules.json": "Индекс модулей", @@ -291,3 +362,13 @@ def _agent_status_advice(status: str, agent_id: str) -> str: if status == "offline": return f"Windows Agent {agent_id} выбран, но сейчас не отвечает. Обновите или запустите агент, затем повторите." return "Для прямого разбора .cf/.cfe нужен выбранный Windows Agent. Укажите его в настройках проекта." + + +def _layout_kind_text(value: str) -> str: + mapping = { + "configuration_with_extensions": "Конфигурация + отдельные папки расширений", + "flat_or_mixed": "Плоская или смешанная выгрузка", + "file": "Отдельный входной файл", + "unknown": "Не определено", + } + return mapping.get(value, value) 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 4791375..96fdb82 100644 --- a/services/api-server/src/api_server/static/html5/html5.css +++ b/services/api-server/src/api_server/static/html5/html5.css @@ -14,12 +14,15 @@ .access-builder{border-bottom:1px solid var(--line);background:#fff}.access-builder-form{display:grid;grid-template-columns:1fr 1fr;gap:10px;padding:12px;border-bottom:1px solid var(--line);background:#fbfcfe}.access-builder-form label{display:grid;gap:5px;font-size:11px;font-weight:900;color:var(--muted);text-transform:uppercase}.access-builder-form input,.access-builder-form textarea{min-width:0;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}.access-builder-form input{height:32px}.access-builder-form textarea{min-height:68px;padding:8px;resize:vertical}.access-builder-actions{grid-column:1/-1;display:flex;gap:8px;justify-content:flex-end}.access-builder-result{border-top: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-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-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: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: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 2be8624..f058006 100644 --- a/services/api-server/tests/test_api.py +++ b/services/api-server/tests/test_api.py @@ -1767,6 +1767,9 @@ def test_ai_structure_prepare_writes_ai_ready_package(tmp_path: Path): "Структура для ИИ", "192.168.220.200", "Пути должны быть доступны серверу", + "Ожидаемый формат выгрузки 1С", + "Конфигурация", + "Без лишнего сырья", "smb_username", "smb_password", "data-ai-structure-progress",