Newer
Older
navi-1 / navi / mcp / ui_server / components / card_grid.py
"""card_grid UI component for navi_ui."""

from __future__ import annotations

from pydantic import BaseModel, Field, field_validator

from .base import UIComponent


def _validate_url(value: str | None) -> str | None:
    """Reject values that are clearly not HTTP/HTTPS URLs."""
    if value is None:
        return value
    value = value.strip()
    if not value.startswith(("http://", "https://")):
        raise ValueError("must be a valid http:// or https:// URL")
    return value


class CardMeta(BaseModel):
    label: str
    value: str


class CardDetail(BaseModel):
    label: str
    value: str


class CardAction(BaseModel):
    label: str
    url: str

    @field_validator("url", mode="before")
    @classmethod
    def _check_url(cls, value: str | None) -> str | None:
        return _validate_url(value)


class CardGridCard(BaseModel):
    id: str = Field(..., min_length=1)
    title: str = Field(..., min_length=1)
    subtitle: str | None = None
    image: str | None = None
    meta: list[CardMeta] = Field(default_factory=list)
    description: str | None = None
    details: list[CardDetail] = Field(default_factory=list)
    actions: list[CardAction] = Field(default_factory=list)

    @field_validator("image", mode="before")
    @classmethod
    def _check_image_url(cls, value: str | None) -> str | None:
        return _validate_url(value)


class CardGridPayload(BaseModel):
    title: str | None = None
    cards: list[CardGridCard] = Field(..., min_length=1, max_length=8)


class CardGrid(UIComponent):
    """Render a flat, borderless grid of up to 4 cards with optional details modal."""

    name = "card_grid"
    description = "A compact grid of cards. Use for search results, listings, or summaries."
    schema = CardGridPayload