Skip to content

Layout System

A layout is the structure of a presentation slide.

It defines what content will be displayed and the relations between different parts of it. Each part of the content can be a primitive type or a component.

We define layouts as Pydantic models, since they are fed into LLMs to guide their output.

Everything is best explained with an example. Let’s imagine a simple layout with two placeholders: one for a headline and one for the content.

from pydantic import BaseModel
class Layout(BaseModel):
headline: str
content: str

Now, let’s suppose we want to add a second headline and content to the layout. We could do this by adding more placeholders:

from pydantic import BaseModel
class Layout(BaseModel):
headline: str
content: str
headline_2: str
content_2: str

However, this is not very flexible. If in the future we want to keep adding more placeholders following the same pattern, we would have to add more fields to the layout. This is not ideal.

Instead, we can offload this repeating pattern to an Item, and then add as many items as we want to the layout.

So, we can redefine our previous layout as follows:

from pydantic import BaseModel
class Item(BaseModel):
headline: str
content: str
class Layout(BaseModel):
items: list[Item]

This way, we can add as many items as we want to the layout in a dynamic way.

A components is, in essence, any indivisible part of content that can not be described by a single primitive type. For example, an image, an icon, a chart…

Components are defined as Pydantic models, and they are used in layouts and items.

Let’s add an image to our layout in the examples above:

from pydantic import BaseModel
class Image(BaseModel):
url: str
caption: str
from pydantic import BaseModel
from image import Image
class Item(BaseModel):
headline: str
content: str
class Layout(BaseModel):
image: Image
items: list[Item]

Here we have added the image to the layout, but nothing stops us from adding it to the item instead. In that case, we would have as many images as items in the slide.

We have seen how Component, Item and Layout can be used to define layouts. However, there is something subtle that is easy to miss, and that would bring a lot of headaches when generating content.

The first option is not great, since it requires us to add specific logic on how to handle each layout or, to have fixed names for content fields.

Instead, we can probe the layout during runtime and dynamically generate the content based on the its structure. For that, we make use of the ComposableItem class. This class exposes two methods:

from pydantic import BaseModel
class ComposableItem(BaseModel):
def has_component(self, component: type[Component]) -> bool:
...
def get_component(self, component: type[Component]) -> Component | None:
...

With these, we can decide what to generate for any given layout. For example:

from image import Image
image = layout.get_component(Image)
if image:
image = generate_image()