Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions CITATION.cff
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,8 @@ keywords:
- Language modeling
- Smart software
license: MIT
version: 7.8.0
date-released: '2026-05-20'
version: 7.8.1
date-released: '2026-06-01'
preferred-citation:
type: conference-paper
authors:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -445,28 +445,52 @@ async def get_single_json_model(buml_file: UploadFile = File(...)):
try:
parsed_project = project_to_json(buml_content)

# Find the first available diagram in the project
if parsed_project.get("ClassDiagram") and parsed_project["ClassDiagram"].get("model"):
diagram_data = parsed_project["ClassDiagram"]
diagram_type = "ClassDiagram"
elif parsed_project.get("ObjectDiagram") and parsed_project["ObjectDiagram"].get("model"):
diagram_data = parsed_project["ObjectDiagram"]
diagram_type = "ObjectDiagram"
elif parsed_project.get("StateMachineDiagram") and parsed_project["StateMachineDiagram"].get("model"):
diagram_data = parsed_project["StateMachineDiagram"]
diagram_type = "StateMachineDiagram"
elif parsed_project.get("AgentDiagram") and parsed_project["AgentDiagram"].get("model"):
diagram_data = parsed_project["AgentDiagram"]
diagram_type = "AgentDiagram"
elif parsed_project.get("GUINoCodeDiagram") and parsed_project["GUINoCodeDiagram"].get("model"):
diagram_data = parsed_project["GUINoCodeDiagram"]
diagram_type = "GUINoCodeDiagram"
elif parsed_project.get("NNDiagram") and parsed_project["NNDiagram"].get("model"):
diagram_data = parsed_project["NNDiagram"]
diagram_type = "NNDiagram"
elif parsed_project.get("QuantumCircuitDiagram") and parsed_project["QuantumCircuitDiagram"].get("model"):
diagram_data = parsed_project["QuantumCircuitDiagram"]
diagram_type = "QuantumCircuitDiagram"
# project_to_json emits the schemaVersion=3 shape:
# { diagrams: { ClassDiagram: [ {id,title,model,...}, ... ], ... },
# currentDiagramType: "...",
# currentDiagramIndices: { ClassDiagram: 0, ... } }
# Pick the currently-active diagram first; fall back through the
# remaining types in the legacy priority order so single-diagram
# endpoints still surface *something* if the active one is empty.
diagrams_map = parsed_project.get("diagrams") or {}
current_indices = parsed_project.get("currentDiagramIndices") or {}
current_type = parsed_project.get("currentDiagramType")

def _entry_is_populated(entry):
model = (entry or {}).get("model") or {}
return bool(
model.get("elements")
or model.get("relationships")
or model.get("pages")
)

def _pick_entry(dtype):
entries = diagrams_map.get(dtype) or []
if not entries:
return None
idx = current_indices.get(dtype, 0)
if not isinstance(idx, int) or idx < 0 or idx >= len(entries):
idx = 0
entry = entries[idx]
return entry if _entry_is_populated(entry) else None

priority = []
if current_type:
priority.append(current_type)
for dtype in (
"ClassDiagram", "ObjectDiagram", "StateMachineDiagram",
"AgentDiagram", "GUINoCodeDiagram", "NNDiagram",
"QuantumCircuitDiagram",
):
if dtype not in priority:
priority.append(dtype)

for dtype in priority:
entry = _pick_entry(dtype)
if entry is not None:
diagram_data = entry
diagram_type = dtype
break

if diagram_data and diagram_data.get("title"):
diagram_title = diagram_data["title"]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,11 @@ def parse_buml_content(content: str) -> DomainModel:
"DateTimeType": PrimitiveDataType("datetime"),
"TimeDeltaType": PrimitiveDataType("timedelta"),
"AnyType": PrimitiveDataType("any"),
# No-op stub: project-exported files often end with a
# `project = Project(name=..., models=[domain_model], ...)` tail.
# The class converter only cares about the DomainModel; swallowing
# the Project(...) call with a stub keeps the sandbox import-tolerant.
"Project": lambda *args, **kwargs: None,
}

# Ensure we have a string before preprocessing
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -446,6 +446,30 @@ def project_to_json(content: str) -> Dict[str, Any]:
if diagram_list:
diagram_jsons[diagram_type] = diagram_list

# Section-header fallback: some project files declare `models=[domain_model]`
# but omit the `# STRUCTURAL MODEL #` (or sibling) section headers entirely —
# all the model code lives in one flat block above the project definition.
# When this happens, `diagram_jsons` ends up empty and every diagram type
# gets an empty placeholder, which the frontend renders as a blank canvas.
# Reuse the single-diagram detector to recover the actual model, then
# restore the project name/description/owner from the Project(...) wrapper.
if not diagram_jsons:
logger.info(
"Project declares models=%s but no section headers were found; "
"falling back to single-diagram detection.", model_names,
)
try:
result = _build_project_from_single_diagram(content)
except ValueError:
# No detectable single-diagram type either — fall through to the
# all-empty default below so the caller sees a structured project.
result = None
if result is not None:
result["name"] = project_name
result["description"] = project_description
result["owner"] = project_owner
return result

project_id = str(uuid.uuid4())
created_at = datetime.now(timezone.utc).isoformat()

Expand Down
2 changes: 1 addition & 1 deletion besser/utilities/web_modeling_editor/frontend
Submodule frontend updated 32 files
+100 −0 packages/editor/src/main/packages/agent-state-diagram/agent-llm/agent-llm.ts
+10 −0 packages/editor/src/main/packages/agent-state-diagram/agent-primitive-colors.ts
+60 −10 packages/editor/src/main/packages/agent-state-diagram/agent-rag-element/agent-rag-element-update.tsx
+21 −1 packages/editor/src/main/packages/agent-state-diagram/agent-rag-element/agent-rag-element.ts
+65 −0 ...ages/editor/src/main/packages/agent-state-diagram/agent-reasoning-state/agent-reasoning-state-component.tsx
+141 −0 packages/editor/src/main/packages/agent-state-diagram/agent-reasoning-state/agent-reasoning-state-update.tsx
+116 −0 packages/editor/src/main/packages/agent-state-diagram/agent-reasoning-state/agent-reasoning-state.ts
+52 −0 packages/editor/src/main/packages/agent-state-diagram/agent-section-elements.ts
+49 −0 packages/editor/src/main/packages/agent-state-diagram/agent-skill/agent-skill-component.tsx
+59 −0 packages/editor/src/main/packages/agent-state-diagram/agent-skill/agent-skill-update.tsx
+67 −0 packages/editor/src/main/packages/agent-state-diagram/agent-skill/agent-skill.ts
+100 −85 packages/editor/src/main/packages/agent-state-diagram/agent-state-preview.ts
+14 −1 packages/editor/src/main/packages/agent-state-diagram/agent-state/agent-state-member.ts
+61 −6 packages/editor/src/main/packages/agent-state-diagram/agent-state/agent-state-update.tsx
+49 −0 packages/editor/src/main/packages/agent-state-diagram/agent-tool/agent-tool-component.tsx
+59 −0 packages/editor/src/main/packages/agent-state-diagram/agent-tool/agent-tool-update.tsx
+67 −0 packages/editor/src/main/packages/agent-state-diagram/agent-tool/agent-tool.ts
+49 −0 packages/editor/src/main/packages/agent-state-diagram/agent-workspace/agent-workspace-component.tsx
+84 −0 packages/editor/src/main/packages/agent-state-diagram/agent-workspace/agent-workspace-update.tsx
+80 −0 packages/editor/src/main/packages/agent-state-diagram/agent-workspace/agent-workspace.ts
+7 −0 packages/editor/src/main/packages/agent-state-diagram/index.ts
+18 −0 packages/editor/src/main/packages/components.ts
+11 −0 packages/editor/src/main/packages/popups.ts
+14 −0 packages/editor/src/main/packages/uml-elements.ts
+1 −0 packages/editor/src/main/typings.ts
+3 −1 packages/webapp/src/main/app/shell/WorkspaceShell.tsx
+602 −59 packages/webapp/src/main/features/agent-config/AgentConfigurationPanel.tsx
+2 −8 packages/webapp/src/main/features/deploy/utils/restoreBaseAgentModels.ts
+4 −21 packages/webapp/src/main/features/generation/dialogs/GeneratorConfigDialogs.tsx
+19 −8 packages/webapp/src/main/features/generation/useGeneratorExecution.ts
+3 −0 packages/webapp/src/main/shared/services/storage/local-storage-repository.ts
+5 −1 packages/webapp/src/main/shared/types/agent-config.ts
1 change: 1 addition & 0 deletions docs/source/releases/v7.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ Version 7
.. toctree::
:maxdepth: 1

v7/v7.8.1
v7/v7.8.0
v7/v7.7.1
v7/v7.7.0
Expand Down
54 changes: 54 additions & 0 deletions docs/source/releases/v7/v7.8.1.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
Version 7.8.1
=============

Patch release: makes **project-exported BUML files load again in the
Web Modeling Editor**. v7.8.0's project import flow rejected
``model.py`` files that embed a ``Project(...)`` wrapper around a flat
domain-model block, returning a "Could not parse BUML file" error or,
worse, silently importing an empty diagram. No metamodel, generator,
or API contract changes — backend-only.

Fixes
-----

* **``parse_buml_content`` sandbox missing ``Project``**: the class
diagram converter's ``exec()`` sandbox in
``besser/utilities/web_modeling_editor/backend/services/converters/buml_to_json/class_diagram_converter.py``
strips ``import`` and ``from`` statements before execution and
injects the metamodel symbols it needs. ``Project`` was never in
that injection list, so any file whose tail looked like
``project = Project(name=..., models=[domain_model], ...)`` failed
with ``NameError: name 'Project' is not defined`` before the
``DomainModel`` could be lifted out. A no-op ``Project`` stub is
now provided in the sandbox so trailing project definitions parse
harmlessly.
* **``project_to_json`` ignored flat project files**: the project
converter at
``besser/utilities/web_modeling_editor/backend/services/converters/buml_to_json/project_converter.py``
only scans for ``# STRUCTURAL MODEL #`` (and the numbered/titled
variants) as section anchors. Files exported with the older flat
layout (``# Classes``, ``# Relationships``, ``# Generalizations``,
``# Domain Model``, then ``# PROJECT DEFINITION #``) hit
``models=[...]`` but produced zero extracted sections, leaving
every diagram empty. When section extraction yields nothing,
``project_to_json`` now falls back to
``_build_project_from_single_diagram`` against the full content
and restores the project ``name``, ``description``, and ``owner``
from the ``Project(...)`` wrapper.
* **``/get-json-model`` read the wrong project shape**: the project
branch in
``besser/utilities/web_modeling_editor/backend/routers/conversion_router.py``
still looked for the legacy flat shape
(``parsed_project["ClassDiagram"]``) instead of the
``schemaVersion=3`` nested shape
(``parsed_project["diagrams"]["ClassDiagram"][i]``). Even after
``project_to_json`` started returning populated diagrams, the
endpoint surfaced ``None`` and raised "Could not parse BUML file."
The branch now honours ``currentDiagramType`` and
``currentDiagramIndices``, falling through the legacy priority
order if the active diagram is empty.

A regression test
(``test_project_to_json_handles_missing_section_headers``) in
``tests/utilities/web_modeling_editor/backend/services/converters/test_project_single_diagram_fallback.py``
guards the section-header-less project case end-to-end.
2 changes: 1 addition & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[metadata]
name = besser
version = 7.8.0
version = 7.8.1
author = Luxembourg Institute of Science and Technology
description = BESSER
long_description = file: README.md
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,69 @@ def test_project_to_json_unknown_file_raises():
project_to_json("x = 1\ny = 2\n")


PROJECT_WITHOUT_SECTION_HEADERS = '''\
from besser.BUML.metamodel.structural import (
Class, Property, BinaryAssociation, DomainModel, Multiplicity,
StringType, IntegerType,
)

book = Class(name="Book")
book_title = Property(name="title", type=StringType)
book.attributes = {book_title}

author = Class(name="Author")
author_name = Property(name="name", type=StringType)
author.attributes = {author_name}

book_author = BinaryAssociation(
name="book_author",
ends={
Property(name="book", type=book, multiplicity=Multiplicity(1, 1)),
Property(name="author", type=author, multiplicity=Multiplicity(1, 9999)),
},
)

library_model = DomainModel(
name="Library",
types={book, author},
associations={book_author},
)

from besser.BUML.metamodel.project import Project
from besser.BUML.metamodel.structural.structural import Metadata

metadata = Metadata(description="A small library project.")
project = Project(
name="Library_Project",
models=[library_model],
owner="Tester",
metadata=metadata,
)
'''


def test_project_to_json_handles_missing_section_headers():
"""Projects with `models=[...]` but no `# STRUCTURAL MODEL #` headers
must still extract the embedded DomainModel — previously this returned
a project where every diagram was empty because the section extractor
found no headers to anchor on."""
project = project_to_json(PROJECT_WITHOUT_SECTION_HEADERS)

# Project metadata from the Project(...) wrapper is preserved.
assert project["name"] == "Library_Project"
assert project["owner"] == "Tester"
assert project["description"] == "A small library project."

class_entry = project["diagrams"]["ClassDiagram"][0]
elements = class_entry["model"]["elements"]
relationships = class_entry["model"]["relationships"]

class_names = {el["name"] for el in elements.values() if el.get("type") == "Class"}
assert class_names == {"Book", "Author"}
# The BinaryAssociation should round-trip into the relationships map.
assert len(relationships) == 1


def test_project_to_json_handles_main_guard():
"""Files with an ``if __name__ == '__main__':`` block should still import.

Expand Down
Loading