Localize AI structure UI and fix form layout
This commit is contained in:
@@ -27,7 +27,7 @@ def prepare_ai_structure(
|
||||
display_output_path: str | None = None,
|
||||
) -> dict[str, Any]:
|
||||
if not input_path.exists():
|
||||
raise FileNotFoundError(f"Input path not found: {input_path}")
|
||||
raise FileNotFoundError(f"Входная папка не найдена: {input_path}")
|
||||
output_path.mkdir(parents=True, exist_ok=True)
|
||||
files = _inventory(input_path)
|
||||
parseable = any(Path(item["relative_path"]).suffix.casefold() in _PARSEABLE_SUFFIXES for item in files)
|
||||
@@ -40,14 +40,14 @@ def prepare_ai_structure(
|
||||
try:
|
||||
normalized = normalize_one_c_project(input_path, project_id=project_id)
|
||||
except Exception as error:
|
||||
diagnostics.append(f"NormalizedProject build failed: {error}")
|
||||
diagnostics.append(f"Не удалось построить NormalizedProject: {error}")
|
||||
elif binaries:
|
||||
diagnostics.append(
|
||||
"Input contains only binary .cf/.cfe files. Server-side AI structure requires Designer DumpConfigToFiles "
|
||||
"or Windows Agent export before semantic indexing."
|
||||
"Во входной папке есть только бинарные файлы .cf/.cfe. Для серверной подготовки структуры нужен "
|
||||
"экспорт Designer DumpConfigToFiles или выгрузка через Windows Agent перед семантической индексацией."
|
||||
)
|
||||
else:
|
||||
diagnostics.append("No 1C metadata/XML/BSL files or .cf/.cfe binaries were found.")
|
||||
diagnostics.append("Во входной папке не найдены файлы метаданных 1С, XML, BSL или бинарные .cf/.cfe.")
|
||||
|
||||
codex_root = output_path / _codex_folder_name(project_id)
|
||||
manifest = _manifest(
|
||||
@@ -255,31 +255,31 @@ def _source_lookup(source_map: list[dict[str, Any]]) -> dict[str, str]:
|
||||
|
||||
|
||||
def _codex_agents_markdown(manifest: dict[str, Any]) -> str:
|
||||
return f"""# AGENTS.md for 1C context package
|
||||
return f"""# AGENTS.md для пакета контекста 1С
|
||||
|
||||
This folder is generated by SFERA for Codex.
|
||||
Эта папка сгенерирована SFERA для Codex.
|
||||
|
||||
## How to use this folder
|
||||
## Как использовать эту папку
|
||||
|
||||
- Treat this package as read-only context for project `{manifest['project_id']}`.
|
||||
- Start with `README.md` and `context/project-overview.md`.
|
||||
- Use `indexes/objects.json`, `indexes/modules.json`, and `indexes/edges.json` for precise navigation.
|
||||
- Use `source/` for local copied BSL/XML/MDO text. Do not rely on the absolute source path from the machine that generated the package.
|
||||
- Use `indexes/source-map.json` to map original source paths to local `source/...` paths.
|
||||
- Use `raw/normalized_project.json` as the authoritative 1C metadata model when present.
|
||||
- 1C modules, forms, commands, реквизиты, табличные части and rights are parts of owner 1C objects. Do not treat a form module as an independent detached source file.
|
||||
- When writing BSL, preserve the owner object context from `qualified_name`, `lineage_id`, and `source`.
|
||||
- If `status` is `export_required`, first export `.cf/.cfe` through 1C Designer/Windows Agent and regenerate this package from the exported files.
|
||||
- Используйте пакет как контекст только для чтения для проекта `{manifest['project_id']}`.
|
||||
- Начинайте с `README.md` и `context/project-overview.md`.
|
||||
- Для точной навигации используйте `indexes/objects.json`, `indexes/modules.json` и `indexes/edges.json`.
|
||||
- Для текста BSL/XML/MDO используйте локальную папку `source/`. Не опирайтесь на абсолютный путь исходников на машине, где пакет был сгенерирован.
|
||||
- Используйте `indexes/source-map.json`, чтобы сопоставлять исходные пути с локальными путями `source/...`.
|
||||
- Если есть `raw/normalized_project.json`, считайте его основной моделью метаданных 1С.
|
||||
- Модули, формы, команды, реквизиты, табличные части и права являются частями объектов 1С-владельцев. Не рассматривайте модуль формы как отдельный независимый файл.
|
||||
- При генерации BSL сохраняйте контекст объекта-владельца из `qualified_name`, `lineage_id` и `source`.
|
||||
- Если `status` равен `export_required`, сначала выгрузите `.cf/.cfe` через 1C Designer/Windows Agent и затем пересоздайте пакет по выгруженным файлам.
|
||||
|
||||
## Important files
|
||||
## Важные файлы
|
||||
|
||||
- `context/project-overview.md` - compact human context.
|
||||
- `context/metadata-tree.md` - metadata tree extracted from NormalizedProject.
|
||||
- `indexes/*.json` - machine-readable indexes for Codex search and reasoning.
|
||||
- `source/` - local UTF-8 copies of BSL/XML/MDO source files.
|
||||
- `objects/*.md` - object-level summaries.
|
||||
- `modules/*.md` - module-level summaries.
|
||||
- `raw/*.json` - full raw SFERA model.
|
||||
- `context/project-overview.md` - краткий контекст для человека.
|
||||
- `context/metadata-tree.md` - дерево метаданных из NormalizedProject.
|
||||
- `indexes/*.json` - машиночитаемые индексы для поиска и рассуждений Codex.
|
||||
- `source/` - локальные UTF-8 копии файлов BSL/XML/MDO.
|
||||
- `objects/*.md` - карточки объектов.
|
||||
- `modules/*.md` - карточки модулей.
|
||||
- `raw/*.json` - полная сырая модель SFERA.
|
||||
"""
|
||||
|
||||
|
||||
@@ -287,31 +287,31 @@ def _codex_readme_markdown(manifest: dict[str, Any]) -> str:
|
||||
snapshot = manifest.get("snapshot") or {}
|
||||
normalized = manifest.get("normalized") or {}
|
||||
lines = [
|
||||
f"# Codex 1C Context: {manifest['project_id']}",
|
||||
f"# Контекст 1С для Codex: {manifest['project_id']}",
|
||||
"",
|
||||
f"- Status: `{manifest['status']}`",
|
||||
f"- Source: `{manifest['input_path']}`",
|
||||
f"- Files scanned: {manifest['files_count']}",
|
||||
f"- SIR nodes: {snapshot.get('nodes', 0)}",
|
||||
f"- SIR edges: {snapshot.get('edges', 0)}",
|
||||
f"- Normalized objects: {normalized.get('objects', 0)}",
|
||||
f"- Статус: `{manifest['status']}`",
|
||||
f"- Источник: `{manifest['input_path']}`",
|
||||
f"- Просканировано файлов: {manifest['files_count']}",
|
||||
f"- Узлов SIR: {snapshot.get('nodes', 0)}",
|
||||
f"- Связей SIR: {snapshot.get('edges', 0)}",
|
||||
f"- Нормализованных объектов: {normalized.get('objects', 0)}",
|
||||
"",
|
||||
"Copy this whole folder into the Codex project when you want Codex to write code for this 1C configuration.",
|
||||
"The package includes a local `source/` folder, so Codex can inspect BSL/XML/MDO files after the folder is moved.",
|
||||
"Перенесите эту папку целиком в проект Codex, когда хотите, чтобы Codex писал код для этой конфигурации 1С.",
|
||||
"Пакет включает локальную папку `source/`, поэтому Codex сможет читать BSL/XML/MDO после переноса папки.",
|
||||
]
|
||||
if manifest.get("diagnostics"):
|
||||
lines.extend(["", "## Diagnostics"])
|
||||
lines.extend(["", "## Диагностика"])
|
||||
lines.extend(f"- {item}" for item in manifest["diagnostics"])
|
||||
return "\n".join(lines) + "\n"
|
||||
|
||||
|
||||
def _codex_start_here_markdown(manifest: dict[str, Any]) -> str:
|
||||
return f"""# Start Here For Codex
|
||||
return f"""# Начните здесь для Codex
|
||||
|
||||
Project: `{manifest['project_id']}`
|
||||
Status: `{manifest['status']}`
|
||||
Проект: `{manifest['project_id']}`
|
||||
Статус: `{manifest['status']}`
|
||||
|
||||
Read in this order:
|
||||
Читайте в таком порядке:
|
||||
|
||||
1. `AGENTS.md`
|
||||
2. `README.md`
|
||||
@@ -322,13 +322,13 @@ Read in this order:
|
||||
7. `indexes/edges.json`
|
||||
8. `source/`
|
||||
|
||||
When generating code:
|
||||
При генерации кода:
|
||||
|
||||
- Locate the owner 1C object first.
|
||||
- Then inspect its module/form/command context.
|
||||
- Prefer local copied files under `source/` for exact source text.
|
||||
- Use `raw/normalized_project.json` when object structure matters more than raw XML layout.
|
||||
- Use `indexes/source-map.json` if you need to map SFERA source references to local package paths.
|
||||
- Сначала найдите объект 1С-владельца.
|
||||
- Затем изучите контекст его модуля, формы и команды.
|
||||
- Для точного текста исходника предпочитайте локальные копии в `source/`.
|
||||
- Используйте `raw/normalized_project.json`, когда структура объекта важнее, чем сырой XML.
|
||||
- Используйте `indexes/source-map.json`, если нужно сопоставить ссылки SFERA с локальными путями пакета.
|
||||
"""
|
||||
|
||||
|
||||
@@ -375,14 +375,14 @@ def _object_markdown(item: dict[str, Any]) -> str:
|
||||
[
|
||||
f"# {item.get('qualified_name') or item.get('name')}",
|
||||
"",
|
||||
f"- Kind: `{item.get('kind')}`",
|
||||
f"- Name: `{item.get('name')}`",
|
||||
f"- Вид: `{item.get('kind')}`",
|
||||
f"- Имя: `{item.get('name')}`",
|
||||
f"- Lineage: `{item.get('lineage_id')}`",
|
||||
f"- Semantic: `{item.get('semantic_id')}`",
|
||||
f"- Source: `{item.get('source')}`",
|
||||
f"- Local source: `{local_source}`",
|
||||
f"- Источник: `{item.get('source')}`",
|
||||
f"- Локальный исходник: `{local_source}`",
|
||||
"",
|
||||
"## Attributes",
|
||||
"## Атрибуты",
|
||||
"```json",
|
||||
json.dumps(item.get("attributes") or {}, ensure_ascii=False, indent=2, default=str),
|
||||
"```",
|
||||
@@ -396,12 +396,12 @@ def _module_markdown(item: dict[str, Any]) -> str:
|
||||
[
|
||||
f"# {item.get('qualified_name') or item.get('name')}",
|
||||
"",
|
||||
f"- Name: `{item.get('name')}`",
|
||||
f"- Имя: `{item.get('name')}`",
|
||||
f"- Lineage: `{item.get('lineage_id')}`",
|
||||
f"- Source: `{item.get('source')}`",
|
||||
f"- Local source: `{local_source}`",
|
||||
f"- Источник: `{item.get('source')}`",
|
||||
f"- Локальный исходник: `{local_source}`",
|
||||
"",
|
||||
"## Module Attributes",
|
||||
"## Атрибуты модуля",
|
||||
"```json",
|
||||
json.dumps(item.get("attributes") or {}, ensure_ascii=False, indent=2, default=str),
|
||||
"```",
|
||||
@@ -431,7 +431,7 @@ def _source_ref_local_path(source_ref: object | None, source_lookup: dict[str, s
|
||||
|
||||
|
||||
def _normalized_tree_markdown(normalized: NormalizedProject) -> str:
|
||||
lines = [f"# Metadata Tree: {normalized.project_id or 'project'}", ""]
|
||||
lines = [f"# Дерево метаданных: {normalized.project_id or 'project'}", ""]
|
||||
for group in normalized.configuration.groups:
|
||||
lines.append(f"## {group.name}")
|
||||
if not group.objects:
|
||||
@@ -481,33 +481,33 @@ def _ai_modules(snapshot: SirSnapshot, source_lookup: dict[str, str] | None = No
|
||||
|
||||
def _ai_context_markdown(manifest: dict[str, Any], snapshot: SirSnapshot | None, normalized: NormalizedProject | None) -> str:
|
||||
lines = [
|
||||
f"# SFERA AI structure: {manifest['project_id']}",
|
||||
f"# Структура SFERA для ИИ: {manifest['project_id']}",
|
||||
"",
|
||||
f"- Status: {manifest['status']}",
|
||||
f"- Source files: {manifest['files_count']}",
|
||||
f"- Artifacts: {', '.join(manifest['artifacts'])}",
|
||||
f"- Статус: {manifest['status']}",
|
||||
f"- Исходных файлов: {manifest['files_count']}",
|
||||
f"- Артефакты: {', '.join(manifest['artifacts'])}",
|
||||
]
|
||||
if snapshot is not None:
|
||||
lines.extend(
|
||||
[
|
||||
f"- SIR nodes: {len(snapshot.nodes)}",
|
||||
f"- SIR edges: {len(snapshot.edges)}",
|
||||
f"- Snapshot hash: {snapshot.snapshot_hash}",
|
||||
f"- Узлов SIR: {len(snapshot.nodes)}",
|
||||
f"- Связей SIR: {len(snapshot.edges)}",
|
||||
f"- Хеш снимка: {snapshot.snapshot_hash}",
|
||||
]
|
||||
)
|
||||
if normalized is not None:
|
||||
lines.append(f"- Normalized metadata groups: {len(normalized.configuration.groups)}")
|
||||
lines.append(f"- Групп нормализованных метаданных: {len(normalized.configuration.groups)}")
|
||||
if manifest["diagnostics"]:
|
||||
lines.append("")
|
||||
lines.append("## Diagnostics")
|
||||
lines.append("## Диагностика")
|
||||
lines.extend(f"- {item}" for item in manifest["diagnostics"])
|
||||
lines.extend(
|
||||
[
|
||||
"",
|
||||
"## How AI should use this package",
|
||||
"- Use `normalized_project.json` as the authoritative 1C object model.",
|
||||
"- Use `sir_snapshot.json`, `ai_objects.json`, `ai_modules.json`, and `ai_edges.json` for code navigation and impact analysis.",
|
||||
"- Treat modules/forms/commands as parts of their owner 1C objects, not as detached text files.",
|
||||
"## Как ИИ должен использовать этот пакет",
|
||||
"- Используйте `normalized_project.json` как основную модель объектов 1С.",
|
||||
"- Используйте `sir_snapshot.json`, `ai_objects.json`, `ai_modules.json` и `ai_edges.json` для навигации по коду и анализа влияния.",
|
||||
"- Рассматривайте модули, формы и команды как части объектов 1С-владельцев, а не как отдельные текстовые файлы.",
|
||||
]
|
||||
)
|
||||
return "\n".join(lines) + "\n"
|
||||
@@ -515,15 +515,15 @@ def _ai_context_markdown(manifest: dict[str, Any], snapshot: SirSnapshot | None,
|
||||
|
||||
def _export_plan_markdown(project_id: str, input_path: Path, output_path: Path, binaries: list[dict[str, Any]], parseable: bool) -> str:
|
||||
lines = [
|
||||
f"# 1C export plan for {project_id}",
|
||||
f"# План выгрузки 1С для {project_id}",
|
||||
"",
|
||||
f"- Input: `{input_path}`",
|
||||
f"- Output: `{output_path}`",
|
||||
f"- Вход: `{input_path}`",
|
||||
f"- Выход: `{output_path}`",
|
||||
]
|
||||
if parseable:
|
||||
lines.append("- Metadata files were found; semantic processing was executed directly.")
|
||||
lines.append("- Найдены файлы метаданных; семантическая обработка выполнена напрямую.")
|
||||
if binaries:
|
||||
lines.extend(["", "## Binary .cf/.cfe files", ""])
|
||||
lines.extend(["", "## Бинарные файлы .cf/.cfe", ""])
|
||||
for item in binaries:
|
||||
lines.append(f"- `{item['relative_path']}`")
|
||||
lines.extend(
|
||||
|
||||
@@ -16,16 +16,16 @@ def render_html5_ai_structure_page(
|
||||
) -> str:
|
||||
project_nav = "\n".join(_project_link(project, project_id) for project in projects)
|
||||
return _page(
|
||||
f"SFERA AI Structure - {project_id}",
|
||||
f"SFERA Структура для ИИ - {project_id}",
|
||||
f"""
|
||||
<main class="workspace ai-structure-workspace" data-html5-page="ai-structure" data-project-id="{escape(project_id)}">
|
||||
{_topbar(project_id, project_nav)}
|
||||
<section class="setup-layout">
|
||||
<aside class="panel">
|
||||
<div class="setup-card">
|
||||
<p class="eyebrow">AI-ready export</p>
|
||||
<p class="eyebrow">Подготовка контекста</p>
|
||||
<h1>Структура для ИИ</h1>
|
||||
<p class="muted">Сервер подготовит полный пакет SFERA: normalized model, SIR graph, объекты, модули, связи и контекст для генерации кода.</p>
|
||||
<p class="muted">Сервер подготовит полный пакет SFERA: нормализованную модель, граф SIR, объекты, модули, связи и контекст для генерации кода.</p>
|
||||
</div>
|
||||
</aside>
|
||||
<section class="panel setup-main">
|
||||
@@ -53,35 +53,35 @@ def render_html5_ai_structure_form(project_id: str, *, saved_credentials: dict[s
|
||||
hx-swap="innerHTML"
|
||||
hx-indicator="[data-ai-structure-progress]"
|
||||
>
|
||||
<label>
|
||||
<label class="ai-structure-field ai-structure-field-wide">
|
||||
<span>Папка с cf/cfe или выгрузкой</span>
|
||||
<input name="input_path" value="\\\\192.168.220.200\\mst\\1c\\MARKA\\CODEX\\CF" />
|
||||
</label>
|
||||
<label>
|
||||
<label class="ai-structure-field ai-structure-field-wide">
|
||||
<span>Папка результата</span>
|
||||
<input name="output_path" value="\\\\192.168.220.200\\mst\\1c\\MARKA\\CODEX\\CODEX" />
|
||||
</label>
|
||||
<label>
|
||||
<span>Project id</span>
|
||||
<label class="ai-structure-field">
|
||||
<span>Идентификатор проекта</span>
|
||||
<input name="project_id" value="{escape(project_id)}" />
|
||||
</label>
|
||||
<label>
|
||||
<label class="ai-structure-field">
|
||||
<span>Домен</span>
|
||||
<input name="smb_domain" value="{escape(saved_domain)}" autocomplete="username" />
|
||||
</label>
|
||||
<label>
|
||||
<label class="ai-structure-field">
|
||||
<span>Логин SMB</span>
|
||||
<input name="smb_username" value="{escape(saved_username)}" autocomplete="username" />
|
||||
</label>
|
||||
<label>
|
||||
<label class="ai-structure-field">
|
||||
<span>Пароль SMB</span>
|
||||
<input name="smb_password" type="password" placeholder="{escape(password_hint)}" autocomplete="current-password" />
|
||||
</label>
|
||||
<label class="checkbox-row">
|
||||
<label class="checkbox-row ai-structure-field ai-structure-field-compact">
|
||||
<input name="save_smb_credentials" type="checkbox" value="1" checked />
|
||||
<span>Сохранить</span>
|
||||
</label>
|
||||
<button class="primary" type="submit">Подготовить для ИИ</button>
|
||||
<button class="primary ai-structure-submit" type="submit">Подготовить для ИИ</button>
|
||||
</form>
|
||||
<section class="ai-structure-progress" data-ai-structure-progress hidden aria-live="polite">
|
||||
<div class="ai-progress-head">
|
||||
@@ -107,21 +107,22 @@ 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 {}
|
||||
status = _status_text(result.get("status"))
|
||||
return f"""
|
||||
<section class="ai-structure-result" data-html5-ai-structure-status="{escape(str(result.get('status', '')))}">
|
||||
<div class="access-plan-head">
|
||||
<span class="status-pill">{escape(str(result.get("status", "")))}</span>
|
||||
<span class="status-pill">{escape(status)}</span>
|
||||
<strong>{escape(str(result.get("codex_package_folder") or result.get("output_path", "")))}</strong>
|
||||
</div>
|
||||
<p class="object-summary">Папка для переноса в Codex: {escape(str(result.get("codex_package_path", "")))}</p>
|
||||
<dl class="setup-metrics">
|
||||
<div><dt>Файлы</dt><dd>{escape(str(result.get("files_count", 0)))}</dd></div>
|
||||
<div><dt>Nodes</dt><dd>{escape(str(snapshot.get("nodes", 0)))}</dd></div>
|
||||
<div><dt>Edges</dt><dd>{escape(str(snapshot.get("edges", 0)))}</dd></div>
|
||||
<div><dt>Objects</dt><dd>{escape(str(normalized.get("objects", 0)))}</dd></div>
|
||||
<div><dt>Узлы</dt><dd>{escape(str(snapshot.get("nodes", 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>
|
||||
</dl>
|
||||
<div class="panel-title">Артефакты</div>
|
||||
<div class="access-operations">{''.join(f'<article class="access-card"><strong>{escape(str(item))}</strong><small>AI structure package</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)}
|
||||
</section>
|
||||
"""
|
||||
@@ -149,3 +150,28 @@ def _diagnostics(items: list[object]) -> str:
|
||||
<div class="panel-title">Диагностика</div>
|
||||
<ul class="access-warnings">{''.join(f'<li>{escape(str(item))}</li>' for item in items)}</ul>
|
||||
"""
|
||||
|
||||
|
||||
def _status_text(value: object) -> str:
|
||||
mapping = {
|
||||
"ready": "готово",
|
||||
"export_required": "нужна выгрузка",
|
||||
"error": "ошибка",
|
||||
}
|
||||
return mapping.get(str(value or ""), str(value or ""))
|
||||
|
||||
|
||||
def _artifact_text(value: object) -> str:
|
||||
mapping = {
|
||||
"manifest.json": "Описание результата",
|
||||
"source_inventory.json": "Список исходных файлов",
|
||||
"ai_context.md": "Контекст для ИИ",
|
||||
"export_plan.md": "План выгрузки",
|
||||
"codex_package": "Папка для Codex",
|
||||
"sir_snapshot.json": "Снимок графа SIR",
|
||||
"ai_objects.json": "Индекс объектов",
|
||||
"ai_modules.json": "Индекс модулей",
|
||||
"ai_edges.json": "Индекс связей",
|
||||
"normalized_project.json": "Нормализованный проект",
|
||||
}
|
||||
return mapping.get(str(value or ""), str(value or ""))
|
||||
|
||||
@@ -13,12 +13,13 @@
|
||||
.access-workspace{background:#f4f7fb}.access-layout{display:grid;grid-template-columns:300px minmax(0,1fr)340px;height:calc(100vh - 88px)}.access-main{border-top:0;border-bottom:0;overflow:auto}.access-nav,.access-side{overflow:auto}.tree-item[data-html5-access-profile-selected="true"]{background:#f8fbff;border-left:3px solid var(--brand);padding-left:9px}.access-empty{margin:16px;border:1px dashed #aeb8c6;background:#fff;padding:18px;color:#687385}.access-empty strong,.access-empty span{display:block}.access-empty strong{color:#1f2937}.access-profile,.access-plan,.access-result{border-bottom:1px solid var(--line);background:#fff}.access-summary{display:flex;gap:8px;flex-wrap:wrap;padding:10px 14px;border-bottom:1px solid var(--line);background:#f8fbff;color:#687385;font-size:12px;font-weight:800}.access-role-grid,.access-operations,.access-list{display:grid}.access-role-grid{grid-template-columns:repeat(2,minmax(0,1fr))}.access-card{display:grid;gap:3px;min-width:0;padding:10px 12px;border-bottom:1px solid var(--line)}.access-role-grid .access-card:nth-child(odd){border-right:1px solid var(--line)}.access-card strong,.access-card small{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.access-card small{color:var(--muted)}.access-plan-head{display:flex;gap:10px;align-items:center;justify-content:space-between;padding:10px 12px;border-bottom:1px solid var(--line);background:#fbfcfe}.access-plan-head form{margin:0}.access-actions{padding:12px}.access-warnings{margin:0;padding:0;list-style:none}.access-warnings li{padding:8px 12px;border-bottom:1px solid var(--line);color:var(--warn);font-weight:800}.access-json{margin:0;max-height:220px;overflow:auto;padding:12px;background:#fbfaf7;border-top:1px solid var(--line);font:12px/1.5 ui-monospace,SFMono-Regular,Consolas,monospace;white-space:pre-wrap}.access-main .primary{background:var(--brand);border-color:var(--brand);color:#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}
|
||||
.ai-structure-form{display:grid;grid-template-columns:1.4fr 1.4fr 180px 120px 180px 180px 120px auto;gap:10px;align-items:end;padding:12px 16px;border-bottom:1px solid var(--line);background:#fbfcfe}.ai-structure-form label{display:grid;gap:5px;font-size:11px;font-weight:900;color:var(--muted);text-transform:uppercase}.ai-structure-form input{height:32px;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}.ai-structure-form .checkbox-row{display:flex;align-items:center;gap:7px;height:32px}.ai-structure-form .checkbox-row input{width:16px;height:16px}.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-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:980px){.ai-structure-form{grid-template-columns:1fr}}
|
||||
@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: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: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}}
|
||||
|
||||
@@ -1720,11 +1720,11 @@ def test_ai_structure_prepare_writes_ai_ready_package(tmp_path: Path):
|
||||
modules_index = json.loads((codex_package / "indexes" / "modules.json").read_text(encoding="utf-8"))
|
||||
assert any(item.get("local_source_path") == "source/Интеграция.bsl" for item in modules_index)
|
||||
module_docs = "\n".join(path.read_text(encoding="utf-8") for path in (codex_package / "modules").glob("*.md"))
|
||||
assert "Local source: `source/Интеграция.bsl`" in module_docs
|
||||
assert "generated by SFERA for Codex" in (codex_package / "AGENTS.md").read_text(encoding="utf-8")
|
||||
assert "Use `source/`" in (codex_package / "AGENTS.md").read_text(encoding="utf-8")
|
||||
assert "Copy this whole folder into the Codex project" in (codex_package / "README.md").read_text(encoding="utf-8")
|
||||
assert "Treat modules/forms/commands as parts" in (output / "ai_context.md").read_text(encoding="utf-8")
|
||||
assert "Локальный исходник: `source/Интеграция.bsl`" in module_docs
|
||||
assert "Эта папка сгенерирована SFERA для Codex" in (codex_package / "AGENTS.md").read_text(encoding="utf-8")
|
||||
assert "локальную папку `source/`" in (codex_package / "AGENTS.md").read_text(encoding="utf-8")
|
||||
assert "Перенесите эту папку целиком в проект Codex" in (codex_package / "README.md").read_text(encoding="utf-8")
|
||||
assert "Рассматривайте модули, формы и команды как части объектов 1С-владельцев" in (output / "ai_context.md").read_text(encoding="utf-8")
|
||||
|
||||
page = client.get("/html5/projects/ai-demo/ai-structure")
|
||||
assert_html5_response_contract(
|
||||
@@ -1738,6 +1738,7 @@ def test_ai_structure_prepare_writes_ai_ready_package(tmp_path: Path):
|
||||
"data-ai-structure-progress",
|
||||
"Осталось примерно",
|
||||
"html5-ai-structure.js",
|
||||
"Идентификатор проекта",
|
||||
full_page=True,
|
||||
)
|
||||
|
||||
@@ -1745,13 +1746,13 @@ def test_ai_structure_prepare_writes_ai_ready_package(tmp_path: Path):
|
||||
"/html5/projects/ai-demo/ai-structure/run",
|
||||
data={"project_id": "ai-demo-html5", "input_path": str(source), "output_path": str(tmp_path / "html5-out")},
|
||||
)
|
||||
assert_html5_response_contract(html5_run, "ready", "codex-1c-context-ai-demo-html5", "sir_snapshot.json", "normalized_project.json")
|
||||
assert_html5_response_contract(html5_run, "готово", "codex-1c-context-ai-demo-html5", "Снимок графа SIR", "Нормализованный проект")
|
||||
|
||||
html5_missing = client.post(
|
||||
"/html5/projects/ai-demo/ai-structure/run",
|
||||
data={"project_id": "ai-demo-html5", "input_path": str(tmp_path / "missing"), "output_path": str(tmp_path / "html5-out")},
|
||||
)
|
||||
assert_html5_response_contract(html5_missing, "ошибка", "Input path not found")
|
||||
assert_html5_response_contract(html5_missing, "ошибка", "Входная папка не найдена")
|
||||
|
||||
html5_smb_without_credentials = client.post(
|
||||
"/html5/projects/ai-demo/ai-structure/run",
|
||||
@@ -1783,7 +1784,7 @@ def test_ai_structure_prepare_reports_cf_cfe_export_required(tmp_path: Path):
|
||||
assert len(payload["binary_1c_files"]) == 2
|
||||
assert "DumpConfigToFiles" in (output / "export_plan.md").read_text(encoding="utf-8")
|
||||
assert (output / payload["codex_package_folder"] / "AGENTS.md").exists()
|
||||
assert "export_required" in (output / payload["codex_package_folder"] / "README.md").read_text(encoding="utf-8")
|
||||
assert "Статус: `export_required`" in (output / payload["codex_package_folder"] / "README.md").read_text(encoding="utf-8")
|
||||
|
||||
|
||||
def test_import_full_replace_replaces_current_normalized_project(tmp_path: Path):
|
||||
|
||||
Reference in New Issue
Block a user