Clarify XML AI structure flow in HTML5 UI
This commit is contained in:
@@ -32,6 +32,7 @@ def render_html5_ai_structure_page(
|
|||||||
<section class="panel setup-main">
|
<section class="panel setup-main">
|
||||||
<div class="panel-title">Подготовка структуры</div>
|
<div class="panel-title">Подготовка структуры</div>
|
||||||
{render_html5_ai_structure_agent_panel(project_id, agent_info=agent_info)}
|
{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_form(project_id, saved_credentials=saved_credentials)}
|
||||||
<div data-html5-ai-structure-path-check>{render_html5_ai_structure_path_check(None)}</div>
|
<div data-html5-ai-structure-path-check>{render_html5_ai_structure_path_check(None)}</div>
|
||||||
<p class="object-summary">Пути должны быть доступны серверу SFERA/API. Для docker-test используйте папку, смонтированную или доступную внутри контейнера; локальные диски Windows и закрытые SMB-папки без учетных данных сервер не увидит.</p>
|
<p class="object-summary">Пути должны быть доступны серверу SFERA/API. Для docker-test используйте папку, смонтированную или доступную внутри контейнера; локальные диски Windows и закрытые SMB-папки без учетных данных сервер не увидит.</p>
|
||||||
@@ -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 """
|
||||||
|
<section class="ai-structure-hint">
|
||||||
|
<div class="access-plan-head">
|
||||||
|
<span class="status-pill">xml/xdt</span>
|
||||||
|
<strong>Ожидаемый формат выгрузки 1С</strong>
|
||||||
|
</div>
|
||||||
|
<div class="ai-structure-hint-grid">
|
||||||
|
<article class="access-card">
|
||||||
|
<strong>Основная конфигурация</strong>
|
||||||
|
<small>Во входной папке должна быть папка <code>Конфигурация</code> с XML/BSL/MDO-файлами выгрузки 1С.</small>
|
||||||
|
</article>
|
||||||
|
<article class="access-card">
|
||||||
|
<strong>Расширения</strong>
|
||||||
|
<small>Каждое расширение кладите в отдельную соседнюю папку с его именем. SFERA загрузит их как части одного проекта.</small>
|
||||||
|
</article>
|
||||||
|
<article class="access-card">
|
||||||
|
<strong>Без лишнего сырья</strong>
|
||||||
|
<small>Для Codex будет собран compact-пакет: краткие индексы, brief-контекст, layout проекта и выборочные исходники вместо полного дубля всей выгрузки.</small>
|
||||||
|
</article>
|
||||||
|
</div>
|
||||||
|
<ul class="access-warnings">
|
||||||
|
<li>Если на входе уже XML-выгрузка, Windows Agent не нужен: сервер сам строит NormalizedProject, SIR и пакет для Codex.</li>
|
||||||
|
<li>Agent нужен только для бинарных <code>.cf</code>/<code>.cfe</code>, когда сначала надо получить XML-структуру из платформы 1С.</li>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
def render_html5_ai_structure_form(project_id: str, *, saved_credentials: dict[str, str] | None = None) -> str:
|
def render_html5_ai_structure_form(project_id: str, *, saved_credentials: dict[str, str] | None = None) -> str:
|
||||||
saved_credentials = saved_credentials or {}
|
saved_credentials = saved_credentials or {}
|
||||||
saved_username = str(saved_credentials.get("username") 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 [])
|
artifacts = list(result.get("artifacts") or [])
|
||||||
snapshot = result.get("snapshot") or {}
|
snapshot = result.get("snapshot") or {}
|
||||||
normalized = result.get("normalized") or {}
|
normalized = result.get("normalized") or {}
|
||||||
|
source_layout = result.get("source_layout") or {}
|
||||||
status = _status_text(result.get("status"))
|
status = _status_text(result.get("status"))
|
||||||
return f"""
|
return f"""
|
||||||
<section class="ai-structure-result" data-html5-ai-structure-status="{escape(str(result.get('status', '')))}">
|
<section class="ai-structure-result" data-html5-ai-structure-status="{escape(str(result.get('status', '')))}">
|
||||||
@@ -192,6 +223,7 @@ def render_html5_ai_structure_result(result: dict | None) -> str:
|
|||||||
<div><dt>Связи</dt><dd>{escape(str(snapshot.get("edges", 0)))}</dd></div>
|
<div><dt>Связи</dt><dd>{escape(str(snapshot.get("edges", 0)))}</dd></div>
|
||||||
<div><dt>Объекты</dt><dd>{escape(str(normalized.get("objects", 0)))}</dd></div>
|
<div><dt>Объекты</dt><dd>{escape(str(normalized.get("objects", 0)))}</dd></div>
|
||||||
</dl>
|
</dl>
|
||||||
|
{render_html5_ai_structure_result_summary(source_layout, normalized)}
|
||||||
<div class="panel-title">Артефакты</div>
|
<div class="panel-title">Артефакты</div>
|
||||||
<div class="access-operations">{''.join(f'<article class="access-card"><strong>{escape(_artifact_text(item))}</strong><small>Файл пакета структуры</small></article>' for item in artifacts)}</div>
|
<div class="access-operations">{''.join(f'<article class="access-card"><strong>{escape(_artifact_text(item))}</strong><small>Файл пакета структуры</small></article>' for item in artifacts)}</div>
|
||||||
{_diagnostics(diagnostics)}
|
{_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"""
|
||||||
|
<div class="panel-title">Структура проекта для Codex</div>
|
||||||
|
<div class="ai-structure-hint-grid">
|
||||||
|
<article class="access-card">
|
||||||
|
<strong>Тип раскладки</strong>
|
||||||
|
<small>{escape(_layout_kind_text(layout_kind))}</small>
|
||||||
|
</article>
|
||||||
|
<article class="access-card">
|
||||||
|
<strong>Главная папка</strong>
|
||||||
|
<small>{escape(main_root)}</small>
|
||||||
|
</article>
|
||||||
|
<article class="access-card">
|
||||||
|
<strong>Расширений</strong>
|
||||||
|
<small>{extensions_count}</small>
|
||||||
|
</article>
|
||||||
|
<article class="access-card">
|
||||||
|
<strong>Первый вход для Codex</strong>
|
||||||
|
<small><code>context/project-brief.md</code>, <code>indexes/project-layout.json</code>, <code>indexes/objects-compact.json</code>, <code>indexes/modules-compact.json</code></small>
|
||||||
|
</article>
|
||||||
|
<article class="access-card">
|
||||||
|
<strong>Исходники</strong>
|
||||||
|
<small>В <code>source/</code> попадает выборочная UTF-8-копия BSL/MDO и ключевых XML, а не полный дубль выгрузки.</small>
|
||||||
|
</article>
|
||||||
|
<article class="access-card">
|
||||||
|
<strong>Папки расширений</strong>
|
||||||
|
<small>{escape(", ".join(extension_roots) or "нет")}</small>
|
||||||
|
</article>
|
||||||
|
</div>
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
def _diagnostics(items: list[object]) -> str:
|
def _diagnostics(items: list[object]) -> str:
|
||||||
if not items:
|
if not items:
|
||||||
return ""
|
return ""
|
||||||
@@ -267,6 +335,9 @@ def _artifact_text(value: object) -> str:
|
|||||||
"ai_context.md": "Контекст для ИИ",
|
"ai_context.md": "Контекст для ИИ",
|
||||||
"export_plan.md": "План выгрузки",
|
"export_plan.md": "План выгрузки",
|
||||||
"codex_package": "Папка для Codex",
|
"codex_package": "Папка для Codex",
|
||||||
|
"project_layout.json": "Карта раскладки проекта",
|
||||||
|
"compact_objects.json": "Компактный индекс объектов",
|
||||||
|
"compact_modules.json": "Компактный индекс модулей",
|
||||||
"sir_snapshot.json": "Снимок графа SIR",
|
"sir_snapshot.json": "Снимок графа SIR",
|
||||||
"ai_objects.json": "Индекс объектов",
|
"ai_objects.json": "Индекс объектов",
|
||||||
"ai_modules.json": "Индекс модулей",
|
"ai_modules.json": "Индекс модулей",
|
||||||
@@ -291,3 +362,13 @@ def _agent_status_advice(status: str, agent_id: str) -> str:
|
|||||||
if status == "offline":
|
if status == "offline":
|
||||||
return f"Windows Agent {agent_id} выбран, но сейчас не отвечает. Обновите или запустите агент, затем повторите."
|
return f"Windows Agent {agent_id} выбран, но сейчас не отвечает. Обновите или запустите агент, затем повторите."
|
||||||
return "Для прямого разбора .cf/.cfe нужен выбранный Windows Agent. Укажите его в настройках проекта."
|
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)
|
||||||
|
|||||||
@@ -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-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}
|
.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-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: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: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}}
|
||||||
|
|||||||
@@ -1767,6 +1767,9 @@ def test_ai_structure_prepare_writes_ai_ready_package(tmp_path: Path):
|
|||||||
"Структура для ИИ",
|
"Структура для ИИ",
|
||||||
"192.168.220.200",
|
"192.168.220.200",
|
||||||
"Пути должны быть доступны серверу",
|
"Пути должны быть доступны серверу",
|
||||||
|
"Ожидаемый формат выгрузки 1С",
|
||||||
|
"Конфигурация",
|
||||||
|
"Без лишнего сырья",
|
||||||
"smb_username",
|
"smb_username",
|
||||||
"smb_password",
|
"smb_password",
|
||||||
"data-ai-structure-progress",
|
"data-ai-structure-progress",
|
||||||
|
|||||||
Reference in New Issue
Block a user