К содержанию
Общее · Разработчикам

Компоненты интерфейса SDK

Меню плагина, таблицы, фильтры, формы, мастера, предпросмотр сообщений и цвет площадки.

Содержание

Плагин описывает, что показать: разделы, поля, таблицы, кнопки и действия. Как это выглядит, решает Lovsel. Каждая функция ui.* возвращает обычный словарь, а кабинет отрисовывает его своими компонентами в стиле shadcn/ui: карточки, таблицы с фильтрами, формы, вкладки, мастера и предпросмотр сообщений. Поэтому плагин выглядит частью Lovsel, работает в светлой и тёмной теме и на телефоне. HTML, CSS и JavaScript писать не нужно.

Структура файла, манифест, события и публикация описаны в руководстве «Как создать плагин». API площадок — в разделах «Плагины для GGsel» и «Плагины для FunPay».

python
from lovsel_sdk import WebPlugin, ui, ValidationError

Цвет площадки

Кнопки, активные пункты меню, переключатели, графики и пузыри сообщений окрашиваются в цвет площадки автоматически. По цвету всегда понятно, с какой площадкой работает пользователь.

ПлагинЦвет
"marketplaces": ["ggsel"]зелёный GGsel
"marketplaces": ["funpay"]голубой FunPay
Обе площадкиплощадка, выбранная в переключателе кабинета; в режиме «Все площадки» — фирменный цвет Lovsel
@app.page(..., marketplace="funpay")цвет этой площадки только на этой странице (для плагина обеих площадок)

Цвет в плагине не задаётся: у блоков есть только смысловые оттенки tone — success, warning, danger, info, neutral и accent (цвет площадки).

У плагина своё меню. Каждая страница — функция, которая возвращает список блоков.

python
app = WebPlugin(MANIFEST, layout="sidebar")

WORKSPACE = "Рабочее пространство"


@app.page("overview", "Обзор", icon="layout-dashboard", group=WORKSPACE)
def overview(ctx, params): ...


@app.page("rules", "Правила", icon="list-checks", group=WORKSPACE, badge=lambda ctx: len(rules(ctx)) or None)
def rules_page(ctx, params): ...


@app.page("rule", "Правило", hidden=True)          # открывается кнопкой, в меню её нет
def rule_page(ctx, params): ...


@app.page("help", "Инструкция", icon="book", bottom=True)
def help_page(ctx, params): ...
ПараметрЧто делает
layout у WebPlugin"sidebar" — меню слева внутри плагина, "tabs" — вкладки сверху, "auto" (по умолчанию) — меню слева, если страниц больше трёх или есть группы. На телефоне меню всегда превращается во вкладки.
iconИмя иконки (список — в конце статьи) или эмодзи.
descriptionПодзаголовок страницы.
groupЗаголовок группы в меню: «Рабочее пространство», «Справка».
badgeФункция badge(ctx): число или строка рядом с названием. {"value": 3, "tone": "danger"} — счётчик другого цвета. None или 0 — без счётчика.
hidden=TrueСтраницы нет в меню: карточка записи, мастер создания. Открывается кнопкой ui.button(page=..., params=...).
bottom=TrueПункт внизу меню («Инструкция»).
marketplaceЦвет площадки для страницы плагина обеих площадок.

Lovsel сам добавляет в меню группу «Сервис»: «Настройки» (если объявлены app.settings), «Журнал» и «О плагине».

Если страница начинается с ui.header, Lovsel показывает его вместо стандартного заголовка страницы.

Параметры страницы

Второй аргумент страницы — params, словарь параметров. Они хранятся в адресе страницы: обновление вкладки и кнопка «Назад» в браузере работают, ссылку на запись можно переслать.

Параметры задают:

  • кнопка ui.button("Открыть", page="rule", params={"id": rule_id});
  • ответ действия ui.result(page="rule", params={"id": rule_id});
  • строка таблицы: кнопка с page= в row_actions или row_click передаёт params["id"] — значение поля row_key строки (по умолчанию id);
  • поле поиска и фильтр в ui.toolbar — параметр с их ключом;
  • вкладки ui.tabs(..., param="tab").
python
@app.page("rule", "Правило", hidden=True)
def rule_page(ctx, params):
    rule = rules(ctx).get(str(params.get("id") or ""))
    if rule is None:
        return [ui.header("Правило не найдено", back="rules"), ui.empty("Правило удалено или ссылка устарела.")]
    ...

Всегда проверяйте, что запись из params существует: ссылка могла устареть.

python
ui.header("Правила", "Товар GGsel → текст благодарности.",
          actions=[ui.button("Новое правило", page="rule_new", variant="primary", icon="plus")],
          back=ui.button("Все правила", page="rules"),
          badge=ui.badge("Работает", "success"),
          eyebrow="Рабочее пространство")

back — id страницы или кнопка; badge — бейдж рядом с заголовком; eyebrow — строка над заголовком. Главная кнопка страницы — одна, с variant="primary".

Раскладка

БлокНазначение
ui.card(title, children, description=, actions=, footer=, tone=, flush=, id=, image=, image_ratio=)Карточка — основной контейнер. actions — кнопки в шапке, footer — кнопки в подвале, tone — подсветка (warning, danger, info, success, accent), flush=True — содержимое без отступов, image — обложка над заголовком (см. «Изображения»).
ui.grid(children, columns=2, widths=[2, 1])Сетка из 1–4 колонок; widths — разная ширина колонок. На телефоне — одна колонка.
ui.section(title, children, description=, actions=)Раздел с подзаголовком без рамки.
ui.tabs([ui.tab(id, label, children, badge=, icon=)], value=, param=, style=)Вкладки внутри страницы. style="underline" — вкладки с подчёркиванием.
ui.details(title, children, description=, open=, icon=)Сворачиваемый блок: редкие настройки, пояснения, опасные действия.
ui.divider(label=)Разделитель, можно с подписью.
python
ui.grid([
    ui.card("Требуют внимания", [ui.list_view(attention, empty_text="Всё отправлено.")]),
    ui.card("Отправки за 14 дней", [ui.chart(days, kind="bar", label="Отправки по дням")]),
], columns=2, widths=[3, 2])

Вкладки. Без param содержимое всех вкладок приходит сразу и переключается без запроса к плагину. С param="tab" выбранная вкладка попадает в params["tab"], страница перерисовывается, и плагин может отдать только нужную вкладку — так делают, когда вкладки тяжёлые.

Текст

БлокНазначение
ui.text(text, tone=, size=, muted=)Абзац. Поддерживает **жирный** и ` код . size: sm, md, lg`.
ui.heading(text, level=2)Подзаголовок.
ui.alert(text, title=, tone=, action=)Заметное сообщение: info, success, warning, danger. action — кнопка справа.
ui.code(text, language=, copy=True)Код или лог моноширинным шрифтом с кнопкой «Скопировать».
ui.image(src, alt=, height=, caption=, ratio=, ...)Картинка: загруженная в Lovsel (ui.asset("имя.png")), https:// или data:image/.... Подробно — в разделе «Изображения».
ui.empty(title, description=, action=, icon=)Пустое состояние: что здесь появится и что сделать.

Пустое состояние обязательно, если данных может не быть:

python
ui.empty("Правил пока нет", "Создайте правило: выберите товар и напишите текст.", icon="message-square",
         action=ui.button("Создать первое правило", page="rule_new", variant="primary"))

Изображения

Баннеры, превью, скриншоты и картинки для инструкций загружаются в кабинете разработчика: «Разработчикам» → плагин → вкладка «Разработка» → «Изображения». В коде на картинку ссылаются по имени файла через ui.asset — Lovsel сам подставит адрес, поэтому картинка одинаково открывается у вас и у всех покупателей, на компьютере и на телефоне. Файл с тем же именем заменяет прежний: код менять не нужно.

БлокНазначение
ui.asset("banner.png")Ссылка на загруженную картинку. Подходит везде, где нужна картинка: ui.image, ui.banner, ui.gallery, ui.card(image=), ui.item(image=), колонка таблицы с format="image". Расширение можно не писать, если имя одно.
ui.image(src, alt=, height=, caption=, ratio=, fit=, width=, zoom=, align=)Картинка. caption — подпись, ratio — пропорции рамки (21:9, 16:9, 3:2, 4:3, 1:1, 3:4), height — высота на компьютере в пикселях, fit="contain" — показать целиком без обрезки, width — наибольшая ширина, align="center" — по центру. По нажатию открывается во весь экран (zoom=False — выключить).
ui.banner(image, title=, description=, actions=, eyebrow=, ratio="21:9", align=, overlay=True)Баннер во всю ширину: картинка, заголовок, текст и кнопки поверх неё. На телефоне баннер становится выше, чтобы текст и кнопки поместились. Без title и description — просто широкая картинка.
ui.gallery(images, columns=3, ratio="4:3", fit=, zoom=True)Галерея: превью, скриншоты, шаги инструкции. Элементы — ui.asset(...), ссылки или {"src": ..., "caption": ...}. На телефоне — две колонки; по нажатию картинки листаются во весь экран.
ui.card(..., image=ui.asset("cover.png"), image_ratio="16:9")Карточка с обложкой.
ui.item(..., image=ui.asset("lot.png"))Миниатюра слева в ui.list_view.
python
@app.page("guide", "Инструкция", icon="book")
def guide(ctx, params):
    return [
        ui.banner(ui.asset("banner.png"), "Автовыдача за 3 шага", "Настройка займёт пару минут.",
                  actions=[ui.button("Начать", page="rules", variant="primary")]),
        ui.gallery([
            {"src": ui.asset("step-1.png"), "caption": "1. Выберите товар"},
            {"src": ui.asset("step-2.png"), "caption": "2. Напишите текст"},
            {"src": ui.asset("step-3.png"), "caption": "3. Включите правило"},
        ], columns=3, ratio="16:9"),
        ui.image(ui.asset("result.png"), caption="Так покупатель увидит сообщение", width=520),
    ]

Ограничения: PNG, JPEG, WebP или GIF, до 2 МБ на файл, до 40 файлов и 20 МБ на плагин. SVG не принимается — в нём может быть скрипт. Тип файла проверяется по содержимому, а не по расширению. Если картинку с указанным именем не загрузили, вместо неё будет заглушка «Изображение не загружено» — тестовый стенд покажет такие места заранее (см. «Проверка страниц»).

Значения

Значения для ячеек таблиц, ui.keyvalue и показателей:

ФункцияРезультат
ui.badge(label, tone, value=, dot=)Бейдж статуса. value — код для фильтров таблицы («on», «off»).
ui.value(v, format=, currency=, subtitle=)Значение с форматом: ui.value(2450, format="money", currency="RUB").
ui.link(label, href)Внешняя ссылка https://.
ui.money(value, currency)Готовая строка суммы: ui.money(1234.5, "USD") → «1 234,50 $».

Валюту берите из данных (order["currency"], ctx.ggsel.currency()), никогда не пишите знак рубля в коде: у площадок и заказов разная валюта.

Показатели и графики

python
ui.stats([
    ui.stat("Отправлено сегодня", 12, icon="send", delta=20, hint="к вчерашнему дню"),
    ui.stat("Выручка", 45200, format="money", currency="RUB", icon="money"),
    ui.stat("Правила работают", "3 из 4", icon="list-checks", page="rules"),
    ui.stat("Ошибки", 2, icon="alert", tone="danger", page="history"),
])

delta — изменение в процентах (бейдж «+20%» зелёный, «−5%» красный), page — открыть страницу по нажатию на карточку, format: number, money, percent. ui.stats(items, columns=) — число колонок, по умолчанию до четырёх.

ui.chart(data, kind=, label=, format=, currency=, height=, compare=):

kindДанные
"area", "line", "bar"Ряд по датам: [{"date": "2026-09-01", "value": 120}, …]. compare — второй ряд пунктиром (прошлый период).
"bars"Горизонтальные полосы: [{"label": "Steam", "value": 12, "hint": "…"}, …].
"donut"Доли: [{"label": "5★", "value": 70}, …].

Таблицы

Таблица в стиле shadcn/ui: сортировка по колонкам, поиск, фильтры, выбор строк, меню действий, страницы и выбор видимых колонок.

python
ui.table(
    [ui.column("product", "Товар GGsel", subtitle="product_meta"),
     ui.column("template", "Текст"),
     ui.column("state", "Статус", format="status"),
     ui.column("sent", "Отправлено", align="right", format="number")],
    rows,
    filters=[ui.filter("state", "Статус", [("on", "Работают"), ("off", "Выключены")], style="tabs")],
    searchable=True, search_placeholder="Поиск по товару или тексту",
    selectable=True,
    bulk_actions=[ui.button("Включить", "bulk_toggle", payload={"on": True}, icon="play")],
    row_click=ui.button("Настроить", page="rule"),
    row_menu=[ui.button("Удалить", "delete_rule", icon="trash", danger=True, confirm="Удалить правило?")],
    footer="4 правила · 3 работают",
)
ПараметрЧто делает
searchable, search_placeholderПоиск по всем колонкам прямо в браузере.
filtersФильтры ui.filter по полям строк. style="tabs" — вкладки со счётчиками над таблицей, "select" — кнопка-фильтр рядом с поиском. Бейдж фильтруется по своему value.
row_actionsКнопки в конце строки. Кнопка с action получает строку в data["row"]; кнопка с page открывает страницу с params["id"].
row_menuПункты меню «⋯» строки — для второстепенных и опасных действий.
row_clickКнопка, которая срабатывает по нажатию на строку: обычно открывает карточку записи.
selectable, bulk_actionsГалочки и действия над выбранными строками: data["ids"] и data["rows"].
row_keyПоле с идентификатором строки (по умолчанию id).
footer, footer_actionsИтог и кнопки под таблицей.
toolbarКнопки справа от поиска.
column_toggleМеню «Колонки». По умолчанию есть, если колонок больше четырёх.
page_sizeСтрок на странице (5–200, по умолчанию 20).
dense=TrueКомпактные строки.
title, description, empty_textЗаголовок, пояснение и текст пустой таблицы.

ui.column(key, label, align=, format=, width=, currency=, subtitle=, sortable=, hidden=, nowrap=):

formatКак показывается значение
textТекст (по умолчанию).
moneyСумма: валюта из поля currency строки или параметра currency колонки.
number, percentЧисло с разрядами, проценты.
date, datetime, relativeДата, дата и время, «3 дн назад». Значение — ISO-строка.
badge, statusБейдж ui.badge; status — бейдж с точкой.
linkСсылка ui.link или https://-строка.
codeМоноширинный текст: ключи, номера.
image, avatarКартинка; аватар с инициалами из текста.
tagsСписок строк бейджами.
progressПолоска прогресса, значение 0–100.
bool«Да» или «Нет».

subtitle — поле строки для второй строки ячейки: «#4389201 · 0,45 ₽/шт.». hidden=True — колонка скрыта, пока пользователь не включит её в меню «Колонки».

Поиск и фильтры страницы

Фильтры таблицы работают в браузере с уже полученными строками. Если данных много и отбирать их должен плагин, используйте панель ui.toolbar: её поиск и фильтры меняют параметры страницы, и страница перерисовывается.

python
@app.page("history", "История", icon="history")
def history_page(ctx, params):
    q = str(params.get("q") or "").lower()
    status = str(params.get("status") or "")
    items = [h for h in history(ctx) if (not status or h["status"] == status) and q in h["order_id"].lower()]
    return [
        ui.toolbar([
            ui.search("q", placeholder="Номер заказа, товар или покупатель"),
            ui.filter("status", "Статус", [("sent", "Отправлено"), ("error", "Ошибка")]),
            ui.button("Очистить историю", "clear_history", variant="ghost", icon="trash", confirm="Очистить?"),
        ]),
        ui.table(COLUMNS, rows_of(items), empty_text="Ничего не найдено."),
    ]

ui.search(param="q", placeholder=) меняет параметр с небольшой задержкой при вводе. ui.filter(key, label, options, style=) в панели — кнопка-фильтр или вкладки (style="tabs"). Кнопки панели прижимаются вправо.

Варианты выбора в ui.filter и полях: "a", ("a", "Подпись") или {"value": "a", "label": "Подпись", "hint": "пояснение", "count": 3}.

Списки, ленты и этапы

БлокНазначение
ui.keyvalue({...} или [(label, value)], columns=1)«Параметр — значение». Значение — текст, ui.badge, ui.value, ui.link.
ui.list_view([ui.item(...)], empty_text=)Список: «Требуют внимания», связки, последние события.
ui.timeline([ui.item(..., time=)], empty_text=)Лента по времени: история заказа, журнал действий.
ui.steps(["Оплачен", "Подтверждён", "Отзыв"], current=1)Этапы процесса; current — номер текущего с нуля.

ui.item(title, description=, badge=, tone=, meta=, action=, actions=, icon=, time=, image=) — элемент списка и ленты (image — миниатюра слева вместо иконки):

python
ui.list_view([
    ui.item("Заказ #10842", "Покупатель закрыл чат", badge="ошибка", tone="danger", meta="Telegram · подписчики",
            action=ui.button("Повторить", "retry", payload={"id": "e1"}, size="sm")),
], empty_text="Все сообщения отправлены.")

Кнопки и действия

ui.button(label, action=, payload=, page=, params=, href=, dialog=, variant=, icon=, confirm=, danger=, disabled=, size=):

  • action — вызвать действие плагина @app.action с payload;
  • page + params — открыть страницу плагина;
  • href — внешняя ссылка https://;
  • dialog — открыть окно ui.dialog без обращения к плагину;
  • confirm — текст подтверждения перед действием;
  • variant: primary (цвет площадки), secondary, outline, ghost, link, soft, danger; size="sm" — маленькая кнопка.
БлокНазначение
ui.actions([buttons], align=)Ряд кнопок; align: start, end, center, between (первая слева, остальные справа).
ui.menu(label, [buttons], icon=, variant=)Кнопка с выпадающим списком действий.
ui.toggle(key, label, value, action=, description=, confirm=)Строка с переключателем: сразу вызывает действие с {"key": key, "value": True/False}. Если действие упало, переключатель вернётся назад.

Действие получает data: payload кнопки, значения формы, строку таблицы (row) или выбранные строки (ids, rows). Что можно вернуть:

РезультатЭффект
None или "Текст"Страница обновится; строка — уведомление «успешно».
ui.result("Текст", tone="info")Уведомление: success, info, warning, danger.
ui.result(page="rule", params={"id": 5})Перейти на страницу плагина.
ui.result(dialog=ui.dialog(...))Открыть окно.
ui.result(operation=op)Показать прогресс запущенной операции.
ui.result(download=ctx.download(...))Скачать файл.
ui.result(errors={"price": "…"})Ошибки у полей формы (то же, что raise ValidationError).
ui.result(values={...})Новые значения полей формы или мастера.
ui.result(close=True)Закрыть окно, из которого вызвано действие.
ui.result(refresh=False)Не перерисовывать страницу.

ui.dialog(title, blocks, description=, size=, side=) — окно: size — sm, md, lg, xl; side=True — панель справа (удобно для карточки записи поверх таблицы).

python
@app.action("show_message")
def show_message(ctx, data):
    row = data.get("row") or {}
    return ui.result(refresh=False, dialog=ui.dialog("Заказ " + row["order_id"], [
        ui.keyvalue([("Покупатель", row["buyer"]), ("Статус", row["status"])]),
        ui.chat([{"text": row["text"], "author": "Вы", "direction": "out", "time": "12:40"}]),
    ], side=True))

Формы

python
ui.form("add_slot", [
    ui.field.section("Товар"),
    ui.field.text("title", "Название", required=True, width="half"),
    ui.field.select("region", "Регион", [("ar", "Аргентина"), ("tr", "Турция")], default="ar", width="half"),
    ui.field.radio("mode", "Режим", [{"value": "auto", "label": "Автоматически", "hint": "По расписанию"},
                                      {"value": "manual", "label": "Вручную"}], default="auto"),
    ui.field.number("markup", "Наценка", min=0, max=200, default=15, suffix="%",
                    show_if={"field": "mode", "equals": "auto"}),
    ui.field.tags("stop_words", "Стоп-слова", placeholder="Слово и Enter"),
    ui.field.file("keys", "Файл с ключами", accept=".txt", max_mb=5),
], submit="Добавить", action="add_slot",
   secondary=[ui.button("Отмена", page="slots", variant="ghost")])
ПолеЗначение в действии
field.text(key, label, placeholder=, secret=, max_length=, prefix=, suffix=)Строка. secret=True — пароль или ключ: не отдаётся в браузер.
field.textarea(key, label, rows=, max_length=)Многострочный текст.
field.number(key, label, min=, max=, step=, prefix=, suffix=)Число.
field.switch(key, label)True или False.
field.select(key, label, options, searchable=, placeholder=)Строка. searchable=True — список с поиском (сотни товаров).
field.radio(key, label, options, cards=True)Строка. Карточки с пояснением из hint варианта.
field.checkboxes(key, label, options)Список строк.
field.tags(key, label)Список фраз: ключевые слова, стоп-слова.
field.template(key, label, variables=)Текст сообщения с переменными и предпросмотром (см. «Сообщения покупателю»).
field.date(key, label)Дата ГГГГ-ММ-ДД.
field.file(key, label, accept=, max_mb=)Объект UploadedFile: name, size, mime, content, text(), lines().
field.hidden(key, value)Скрытое значение, например id записи.
field.section(title, description=)Подзаголовок внутри длинной формы.

Общие параметры полей: hint, placeholder, required, default, disabled, width="half" (два поля в ряд), show_if={"field": "mode", "equals": "manual"} или {"field": "mode", "in": ["a", "b"]} — показывать поле, только если другое поле равно значению. Скрытые условием поля не проверяются и не отправляются.

ui.form(id, fields, submit=, action=, values=, description=, danger=, secondary=, columns=2): values — текущие значения, secondary — кнопки слева от «Сохранить», columns=1 — все поля в одну колонку.

Lovsel проверяет обязательные поля и числа ещё до вызова действия и приводит значения к типам. Ошибку, которую видит только плагин, покажите у поля:

python
if price <= 0:
    raise ValidationError({"price": "Цена должна быть больше нуля"})

Не просите пользователя вводить данные по одному сообщению («введите название», «теперь цену»): одна форма — одно действие.

Мастер

Пошаговая форма для создания сложной записи: «1 · Товар → 2 · Сообщение → 3 · Проверка».

python
ui.wizard("new_rule", [
    ui.step("product", "Товар", [
        ui.field.select("product_id", "Товар GGsel", options, searchable=True, required=True),
    ], description="Какой товар подключить?"),
    ui.step("message", "Сообщение", [
        ui.field.template("template", "Текст благодарности", variables=VARIABLES, required=True),
        ui.field.switch("enabled", "Включить сразу", default=True),
    ]),
    ui.step("review", "Проверка", review=True, blocks=[ui.alert("Одно сообщение на заказ.", tone="info")]),
], action="create_rule", submit="Создать правило", validate="check_rule",
   cancel=ui.button("Отмена", page="rules", variant="ghost"))
  • «Далее» проверяет обязательные поля текущего шага.
  • validate — действие, которое вызывается на каждом шаге с data["__step"] = id шага. Оно может raise ValidationError({...}) или вернуть ui.result(values={...}), чтобы заполнить следующие шаги.
  • review=True — итоговый шаг: Lovsel сам покажет все введённые значения.
  • На последнем шаге вызывается action со всеми значениями. Обычно он возвращает ui.result("Создано", page="rule", params={"id": new_id}).

Сообщения покупателю

Для плагинов, которые пишут покупателям, есть редактор шаблона и предпросмотр в стиле чата площадки.

python
VARIABLES = [
    ui.var("buyer", "Имя покупателя", "Алексей"),
    ui.var("product", "Название товара", "Telegram · подписчики"),
    ui.var("order_id", "Номер заказа", "10842"),
]

ui.form("save_text", [
    ui.field.template("text", "Текст сообщения", variables=VARIABLES, required=True),
], values={"text": current_text}, submit="Сохранить текст")
  • ui.var(key, label, example) — переменная: {buyer} в тексте, «Алексей» в предпросмотре.
  • field.template(key, label, variables=, rows=, preview=True, preview_title=) — поле с чипами переменных (вставляются туда, где стоит курсор) и живым предпросмотром рядом.
  • ui.message_preview(text, variables=, title=, author=, note=, marketplace=) — готовое сообщение глазами покупателя.
  • ui.chat(messages, title=, marketplace=) — переписка: [{"text": "…", "author": "Покупатель", "direction": "in", "time": "12:40"}]. Исходящие сообщения ("out") окрашены в цвет площадки.

Подставлять переменные в настоящий текст плагин должен сам (text.replace("{buyer}", name)): Lovsel лишь показывает пример.

Прогресс, операции и журнал

БлокНазначение
ui.progress(value, max, label=)Статичный прогресс: «14 из 50».
ui.progress(operation=op.id)Живой прогресс запущенной операции.
ui.operations(limit=10)Все операции плагина с прогрессом и кнопкой «Остановить».
ui.logs(limit=100, filters=True)Журнал плагина (ctx.log) с поиском и фильтром по уровню.

Ошибки обработчиков Lovsel записывает в журнал сам — с трассировкой до строки вашего файла. Автор видит её во вкладке «Журнал» и в кабинете разработчика.

Иконки

Иконки задаются именем: activity, alert, arrow-left, arrow-right, bar-chart, bell, book, bot, box, calendar, card, cart, chart, check, check-circle, clock, copy, dashboard, download, edit, external, eye, file, file-text, filter, gift, globe, hash, heart, help, history, image, inbox, info, key, layers, layout-dashboard, link, list, list-checks, lock, mail, message, message-square, messages, money, more, order, package, panel, pause, percent, play, plus, receipt, refresh, rocket, search, send, settings, shield, sliders, sparkles, star, stop, store, table, tag, trash, trend, truck, upload, user, users, wallet, wand, x, x-circle, zap. Можно указать эмодзи.

Проверка страниц

Тестовый стенд отрисовывает каждую страницу и проверяет блоки: неизвестный тип блока, кнопку без действия, повторяющийся ключ поля или картинку с неверным адресом он покажет с путём до места ошибки. Если передать папку с картинками плагина (assets="images/"), стенд проверит, что каждая ui.asset("имя") ссылается на существующий файл.

bash
python -m lovsel_sdk.testing my_plugin.py
python
from lovsel_sdk.testing import load_plugin

plugin = load_plugin("my_plugin.py", products=[{"id": 101, "title": "Ключ Steam", "price": 199}],
                     assets="images/")
blocks = plugin.page("rules")                   # ValueError, если блок описан с ошибкой
plugin.action("create_rule", {"product_id": "101", "template": "Спасибо!", "enabled": True})
plugin.emit("new_order", {"order": {"id": "9001", "product_id": 101, "chat_id": 9001}})
assert plugin.ggsel_sent[0]["text"] == "Спасибо!"

Готовые образцы

Список и карточка записи. Страница со списком — таблица с row_click=ui.button("Открыть", page="item"). Скрытая страница @app.page("item", ..., hidden=True) читает params["id"], начинается с ui.header(..., back=ui.button("Все записи", page="items")) и показывает ui.keyvalue, историю ui.timeline и опасные действия в ui.details.

Создание записи. Кнопка «Новая запись» в шапке списка ведёт на скрытую страницу с ui.wizard. Действие мастера возвращает ui.result("Создано", page="item", params={"id": new_id}).

Сценарии во вкладках. ui.tabs с вкладкой на сценарий: в каждой ui.toggle «Включено» и форма с field.template. Так устроены приветствие, просьба об отзыве и ответ на отзыв в примере для FunPay.

Журнал с поиском. ui.toolbar([ui.search(...), ui.filter(...)]) и таблица, которую плагин отбирает по params; нажатие на строку открывает ui.dialog(..., side=True) с подробностями.

Обзор. ui.header с главной кнопкой, ui.stats на четыре показателя, ui.grid с «Требуют внимания» и графиком, последние события в ui.timeline.

Полные примеры

  • GGsel — «Благодарности покупателям»: меню слева, правила с мастером, шаблон с предпросмотром, история с поиском и повторной отправкой. Файл docs/examples/ggsel_thanks.py, разбор — в статье «Плагины для GGsel».
  • FunPay — «Отзывы и напоминания»: сценарии во вкладках, заказы с этапами, отзывы с фильтрами, переписка в окне сбоку. Файл docs/examples/funpay_reviews.py, разбор — в статье «Плагины для FunPay».

Оба примера можно скачать кнопкой «Пример плагина» в статье площадки, загрузить в «Плагины → Разработчикам» и открыть у себя — без публикации в магазине.