Skip to content

widgets

WidgetTemplate

Utility for loading and building widgets from a .widget file.

Example using .widget file on disc:

template = WidgetTemplate.from_file("path/to/my_widget.widget")
widget = template.build({"name": "Harry Potter"})

Example using already parsed widget definition:

template = WidgetTemplate(definition={"version": "1.0", "name": "...", "template": Template(...), "jsonSchema": {...}})
widget = template.build({"name": "Harry Potter"})

Source code in chatkit/widgets.py
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
class WidgetTemplate:
    """
    Utility for loading and building widgets from a .widget file.

    Example using .widget file on disc:
    ```python
    template = WidgetTemplate.from_file("path/to/my_widget.widget")
    widget = template.build({"name": "Harry Potter"})
    ```

    Example using already parsed widget definition:
    ```python
    template = WidgetTemplate(definition={"version": "1.0", "name": "...", "template": Template(...), "jsonSchema": {...}})
    widget = template.build({"name": "Harry Potter"})
    ```
    """

    def __init__(self, definition: dict[str, Any]):
        self.version = definition["version"]
        if self.version != "1.0":
            raise ValueError(f"Unsupported widget spec version: {self.version}")

        self.name = definition["name"]
        template = definition["template"]
        if isinstance(template, Template):
            self.template = template
        else:
            self.template = _jinja_env.from_string(template)
        self.data_schema = definition.get("jsonSchema", {})

    @classmethod
    def from_file(cls, file_path: str) -> WidgetTemplate:
        path = Path(file_path)
        if not path.is_absolute():
            caller_frame = inspect.stack()[1]
            caller_path = Path(caller_frame.filename).resolve()
            path = caller_path.parent / path

        with path.open("r", encoding="utf-8") as file:
            payload = json.load(file)

        return cls(payload)

    def build(
        self, data: dict[str, Any] | BaseModel | None = None
    ) -> DynamicWidgetRoot:
        """Render the widget template with the given data and return a DynamicWidgetRoot instance."""
        rendered = self.template.render(**self._normalize_data(data))
        widget_dict = json.loads(rendered)
        return DynamicWidgetRoot.model_validate(widget_dict)

    def build_basic(self, data: dict[str, Any] | BaseModel | None = None) -> BasicRoot:
        """Separate method for building basic root widgets until BasicRoot is supported for streamed widgets."""
        rendered = self.template.render(**self._normalize_data(data))
        widget_dict = json.loads(rendered)
        return BasicRoot.model_validate(widget_dict)

    def _normalize_data(
        self, data: dict[str, Any] | BaseModel | None
    ) -> dict[str, Any]:
        if data is None:
            return {}
        return data.model_dump() if isinstance(data, BaseModel) else data

build

build(
    data: dict[str, Any] | BaseModel | None = None,
) -> DynamicWidgetRoot

Render the widget template with the given data and return a DynamicWidgetRoot instance.

Source code in chatkit/widgets.py
1171
1172
1173
1174
1175
1176
1177
def build(
    self, data: dict[str, Any] | BaseModel | None = None
) -> DynamicWidgetRoot:
    """Render the widget template with the given data and return a DynamicWidgetRoot instance."""
    rendered = self.template.render(**self._normalize_data(data))
    widget_dict = json.loads(rendered)
    return DynamicWidgetRoot.model_validate(widget_dict)

build_basic

build_basic(
    data: dict[str, Any] | BaseModel | None = None,
) -> BasicRoot

Separate method for building basic root widgets until BasicRoot is supported for streamed widgets.

Source code in chatkit/widgets.py
1179
1180
1181
1182
1183
def build_basic(self, data: dict[str, Any] | BaseModel | None = None) -> BasicRoot:
    """Separate method for building basic root widgets until BasicRoot is supported for streamed widgets."""
    rendered = self.template.render(**self._normalize_data(data))
    widget_dict = json.loads(rendered)
    return BasicRoot.model_validate(widget_dict)

DynamicWidgetRoot

Bases: DynamicWidgetComponent

Dynamic root widget restricted to root types.

Source code in chatkit/widgets.py
1105
1106
1107
1108
class DynamicWidgetRoot(DynamicWidgetComponent):
    """Dynamic root widget restricted to root types."""

    type: Literal["Card", "ListView"]  # pyright: ignore

BasicRoot

Bases: DynamicWidgetComponent

Layout root capable of nesting components or other roots.

Source code in chatkit/widgets.py
1111
1112
1113
1114
class BasicRoot(DynamicWidgetComponent):
    """Layout root capable of nesting components or other roots."""

    type: Literal["Basic"] = Field(default="Basic", frozen=True)  # pyright: ignore

DynamicWidgetComponent

Bases: WidgetComponentBase

A widget component with a statically defined base shape but dynamically defined additional fields loaded from a widget template or JSON schema.

Source code in chatkit/widgets.py
1060
1061
1062
1063
1064
1065
1066
1067
class DynamicWidgetComponent(WidgetComponentBase):
    """
    A widget component with a statically defined base shape but dynamically
    defined additional fields loaded from a widget template or JSON schema.
    """

    model_config = ConfigDict(extra="allow")
    children: DynamicWidgetComponent | list[DynamicWidgetComponent] | None = None