From 0a9ab946796b4850dbf14fc315571c11c354c4d5 Mon Sep 17 00:00:00 2001 From: Mikhail Date: Fri, 22 May 2026 15:43:24 +0300 Subject: [PATCH] Add source preview to AI structure package --- .../src/api_server/ai_structure_service.py | 39 +++++++++++++++++++ .../src/api_server/html5_ai_structure.py | 3 ++ services/api-server/src/api_server/main.py | 1 + services/api-server/tests/test_api.py | 3 ++ 4 files changed, 46 insertions(+) diff --git a/services/api-server/src/api_server/ai_structure_service.py b/services/api-server/src/api_server/ai_structure_service.py index df0d55b..7047483 100644 --- a/services/api-server/src/api_server/ai_structure_service.py +++ b/services/api-server/src/api_server/ai_structure_service.py @@ -62,9 +62,11 @@ def prepare_ai_structure( diagnostics, binaries, _source_layout_summary(input_path), + _source_preview_summary(input_path), ) _write_json(output_path / "manifest.json", manifest) _write_json(output_path / "source_inventory.json", {"files": files}) + _write_json(output_path / "source_preview.json", manifest.get("source_preview") or []) if snapshot is not None: (output_path / "sir_snapshot.json").write_bytes(snapshot_to_json(snapshot)) _write_json(output_path / "ai_objects.json", _ai_objects(snapshot)) @@ -104,6 +106,7 @@ def _manifest( diagnostics: list[str], binaries: list[dict[str, Any]], source_layout: dict[str, Any], + source_preview: list[dict[str, Any]], ) -> dict[str, Any]: return { "version": AI_STRUCTURE_VERSION, @@ -116,6 +119,7 @@ def _manifest( "files_count": len(files), "binary_1c_files": binaries, "source_layout": source_layout, + "source_preview": source_preview, "artifacts": _artifacts(snapshot, normalized), "snapshot": None if snapshot is None @@ -148,6 +152,7 @@ def _artifacts(snapshot: SirSnapshot | None, normalized: NormalizedProject | Non "export_plan.md", "codex_package", "project_layout.json", + "source_preview.json", "compact_objects.json", "compact_modules.json", ] @@ -201,6 +206,7 @@ def _write_codex_package( _write_json(root / "indexes" / "source-inventory.json", {"files": files}) _write_json(root / "indexes" / "source-map.json", {"files": source_map}) _write_json(root / "indexes" / "project-layout.json", manifest.get("source_layout") or {}) + _write_json(root / "indexes" / "source-preview.json", manifest.get("source_preview") or []) _write_json(root / "indexes" / "objects-compact.json", compact_objects) _write_json(root / "indexes" / "modules-compact.json", compact_modules) _write_json(root / "compact" / "objects.json", compact_objects) @@ -592,6 +598,29 @@ def _source_layout_summary(root: Path) -> dict[str, Any]: } +def _source_preview_summary(root: Path) -> list[dict[str, Any]]: + files = [root] if root.is_file() else sorted(path for path in root.rglob("*") if path.is_file()) + object_files = _preview_relative_paths( + root, + sorted( + [path for path in files if path.suffix.casefold() in {".xml", ".mdo"}], + key=lambda path: ( + 0 if any(part.casefold() in {"configuration", "конфигурация"} for part in path.parts) else 1, + str(path).casefold(), + ), + ), + limit=6, + ) + module_files = _preview_relative_paths(root, [path for path in files if path.suffix.casefold() == ".bsl"], limit=6) + layout = _source_layout_summary(root) + return [ + {"label": "Главная конфигурация", "items": [layout["main_configuration_root"]]}, + {"label": "Папки расширений", "items": layout["extension_roots"] or ["нет"]}, + {"label": "Первые файлы объектов", "items": object_files or ["не найдены"]}, + {"label": "Первые файлы модулей", "items": module_files or ["не найдены"]}, + ] + + def _compact_objects(normalized: NormalizedProject | None) -> list[dict[str, Any]]: if normalized is None: return [] @@ -679,6 +708,16 @@ def _project_brief_markdown(manifest: dict[str, Any], compact_objects: list[dict return "\n".join(lines) + "\n" +def _preview_relative_paths(root: Path, files: list[Path], *, limit: int) -> list[str]: + preview: list[str] = [] + for path in files[:limit]: + if root.is_file(): + preview.append(path.name) + else: + preview.append(path.relative_to(root).as_posix()) + return preview + + def _export_plan_markdown(project_id: str, input_path: Path, output_path: Path, binaries: list[dict[str, Any]], parseable: bool) -> str: lines = [ f"# План выгрузки 1С для {project_id}", 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 bb4d488..b21d87f 100644 --- a/services/api-server/src/api_server/html5_ai_structure.py +++ b/services/api-server/src/api_server/html5_ai_structure.py @@ -210,6 +210,7 @@ def render_html5_ai_structure_result(result: dict | None) -> str: snapshot = result.get("snapshot") or {} normalized = result.get("normalized") or {} source_layout = result.get("source_layout") or {} + source_preview = list(result.get("source_preview") or []) status = _status_text(result.get("status")) return f"""
@@ -225,6 +226,7 @@ def render_html5_ai_structure_result(result: dict | None) -> str:
Объекты
{escape(str(normalized.get("objects", 0)))}
{render_html5_ai_structure_result_summary(source_layout, normalized)} + {_render_ai_structure_preview_tree(source_preview)}
Артефакты
{''.join(f'
{escape(_artifact_text(item))}Файл пакета структуры
' for item in artifacts)}
{_diagnostics(diagnostics)} @@ -365,6 +367,7 @@ def _artifact_text(value: object) -> str: "export_plan.md": "План выгрузки", "codex_package": "Папка для Codex", "project_layout.json": "Карта раскладки проекта", + "source_preview.json": "Краткий предпросмотр структуры", "compact_objects.json": "Компактный индекс объектов", "compact_modules.json": "Компактный индекс модулей", "sir_snapshot.json": "Снимок графа SIR", diff --git a/services/api-server/src/api_server/main.py b/services/api-server/src/api_server/main.py index 9978c03..6130b35 100644 --- a/services/api-server/src/api_server/main.py +++ b/services/api-server/src/api_server/main.py @@ -1409,6 +1409,7 @@ class AiStructurePrepareResponse(BaseModel): files_count: int = 0 binary_1c_files: list[dict] = Field(default_factory=list) source_layout: dict = Field(default_factory=dict) + source_preview: list[dict] = Field(default_factory=list) artifacts: list[str] = Field(default_factory=list) snapshot: dict | None = None normalized: dict | None = None diff --git a/services/api-server/tests/test_api.py b/services/api-server/tests/test_api.py index 57d42f7..b49ab29 100644 --- a/services/api-server/tests/test_api.py +++ b/services/api-server/tests/test_api.py @@ -1731,6 +1731,7 @@ def test_ai_structure_prepare_writes_ai_ready_package(tmp_path: Path): assert (output / "normalized_project.json").exists() assert (output / "sir_snapshot.json").exists() assert (output / "project_layout.json").exists() + assert (output / "source_preview.json").exists() assert (output / "compact_objects.json").exists() assert (output / "compact_modules.json").exists() assert (codex_package / "AGENTS.md").exists() @@ -1740,6 +1741,7 @@ def test_ai_structure_prepare_writes_ai_ready_package(tmp_path: Path): assert (codex_package / "context" / "project-overview.md").exists() assert (codex_package / "indexes" / "codex-navigation.json").exists() assert (codex_package / "indexes" / "project-layout.json").exists() + assert (codex_package / "indexes" / "source-preview.json").exists() assert (codex_package / "indexes" / "objects-compact.json").exists() assert (codex_package / "indexes" / "modules-compact.json").exists() assert (codex_package / "indexes" / "objects.json").exists() @@ -1842,6 +1844,7 @@ def test_ai_structure_prepare_understands_configuration_and_extension_folders(tm assert payload["status"] == "ready" assert payload["source_layout"]["main_configuration_root"] == "Конфигурация" assert payload["source_layout"]["extension_roots"] == ["CRM"] + assert any(item["label"] == "Папки расширений" for item in payload["source_preview"]) assert payload["normalized"]["extensions"] == 1 codex_package = output / payload["codex_package_folder"] layout = json.loads((codex_package / "indexes" / "project-layout.json").read_text(encoding="utf-8"))