Найти в Дзене
Один Rust не п...Rust

Backend-driven UI c ML

t.me/oneRustnoqRust Для чего нужна данная статья? : Создать Backend-driven UI с ML - подход к построению пользовательского интерфейса, при котором логика и отображение контента контролируются сервером (backend) в зависимости от возраста пользователя. Зачем Вам это уметь? : Сделать так чтобы серверы могли работать одновременно и не блокировать друг друга. Rocket и Темплейты Пример:
Представьте, что у вас есть сайт с погодой. Rocket обрабатывает запросы пользователей (например, "покажи погоду в Москве"), а темплейты формируют красивую страницу с прогнозом, подставляя актуальные данные. Actix и API Пример:
Вы пишете мобильное приложение для заказа еды. Actix помогает создать API, через который приложение будет отправлять заказы на кухню, а кухня — возвращать статус готовности. Warp и WebSocket для Real-time обновлений Пример:
Представьте чат, где сообщения появляются сразу, как только их отправляют. Или биржевой тикер, где цены акций обновляются в реальном времени. Warp и WebSocket как р
Оглавление
nicktretyakov1/backend_ui | Gitverse
ML на RUST без заморочек

t.me/oneRustnoqRust

Для чего нужна данная статья? :

Создать Backend-driven UI с ML - подход к построению пользовательского интерфейса, при котором логика и отображение контента контролируются сервером (backend) в зависимости от возраста пользователя.

Зачем Вам это уметь? :

Сделать так чтобы серверы могли работать одновременно и не блокировать друг друга.

Варианты использования backend-driven UI:

Rocket и Темплейты

  • Rocket — это фреймворк для создания веб-приложений на языке Rust. Он помогает быстро и удобно писать серверную часть сайта (backend).
  • Темплейты — это шаблоны, которые позволяют динамически генерировать HTML-страницы. Например, вы можете создать шаблон для страницы новостей, который будет автоматически подставлять актуальные данные из базы.

Пример:
Представьте, что у вас есть сайт с погодой. Rocket обрабатывает запросы пользователей (например, "покажи погоду в Москве"), а темплейты формируют красивую страницу с прогнозом, подставляя актуальные данные.

Actix и API

  • Actix — ещё один популярный фреймворк для Rust, который часто используется для создания API.
  • API (Application Programming Interface) — это набор правил, по которым программы общаются между собой. Например, когда вы заходите в приложение погоды на телефоне, оно через API запрашивает данные с сервера.

Пример:
Вы пишете мобильное приложение для заказа еды. Actix помогает создать API, через который приложение будет отправлять заказы на кухню, а кухня — возвращать статус готовности.

Warp и WebSocket для Real-time обновлений

  • Warp — это лёгкий и быстрый фреймворк для Rust, который удобно использовать для работы с WebSocket.
  • WebSocket — технология, которая позволяет серверу и клиенту (например, браузеру) обмениваться данными в реальном времени, без необходимости постоянно перезагружать страницу.
  • Real-time обновления — это когда изменения на сайте или в приложении происходят мгновенно, как только появляются новые данные.

Пример:
Представьте чат, где сообщения появляются сразу, как только их отправляют. Или биржевой тикер, где цены акций обновляются в реальном времени. Warp и WebSocket как раз помогают реализовать такие функции.

Как объединить backend-driven UI с Rocket, Actix и API, Warp и WebSocket для real-time обновлений?

Нужно реализовать многофреймворковый бэкенд Rust, API и обновления в режиме реального времени, используя ML для персонализации порядка элементов пользовательского интерфейса.
Нужно одновременно запустить три отдельных веб-сервера: Rocket для шаблонного рендеринга HTML, Actix-web для ответов JSON API и Warp для подключений WebSocket.
Нужно извлечь данные пользовательского интерфейса из API, проверить элементы, применить модель линейной регрессии для их упорядочивания и вывести представление на консоль.


Основные компоненты


1.
Backend-driven UI

Что это?
Это подход, при котором внешний вид и логика интерфейса (то, как выглядит приложение или сайт) управляются не только кодом на стороне клиента (например, в браузере), но и сервером (backend). Сервер отправляет клиенту описание интерфейса, а клиент просто отображает его.

Пример:
Представьте, что вы заходите в приложение новостей. Вместо того, чтобы разработчики заранее прописывали, где будет заголовок, где картинка, а где кнопка "Подробнее", сервер отправляет вашему телефону JSON-файл с описанием: "Покажи заголовок здесь, картинку здесь, а кнопку — под ними". Так можно быстро менять дизайн, не обновляя само приложение.

2. Структуры пользовательского интерфейса в формате JSON

Что это?
JSON — это удобный формат для описания данных, похожий на список или таблицу. Здесь он используется, чтобы описать, из каких элементов состоит интерфейс и как они расположены.

Пример:

jsonCopy{
"type": "страница",
"элементы": [
{"тип": "заголовок", "текст": "Добро пожаловать!"},
{"тип": "картинка", "ссылка": "logo.png"},
{"тип": "кнопка", "текст": "Начать"}
]
}

Это описание говорит: "Покажи заголовок, под ним картинку, а под картинкой — кнопку".

3. UIElement и вложенные компоненты

Что это?
UIElement — это любой элемент интерфейса (кнопка, текст, картинка и т.д.). Вложенные компоненты — это когда один элемент содержит внутри себя другие элементы, как матрешка.

Пример:
Контейнер "Блок новости" может содержать:

  • Заголовок
  • Картинку
  • Текст новости
  • Кнопку "Читать дальше"
    А сам блок новости может быть вложен в контейнер "Лента новостей".

4. ML (машинное обучение) для адаптации порядка отображения

Что это?
Машинное обучение анализирует, как пользователи взаимодействуют с интерфейсом, и автоматически меняет порядок элементов, чтобы сделать его удобнее.

Пример:
Если большинство пользователей сначала кликают на кнопку "Скидки", а потом на "Новинки", ML может поменять их местами, чтобы кнопка "Скидки" была выше.

5. ML, обученная на фиктивных данных, прогнозирует индекс для переупорядочивания

Что это?
Модель машинного обучения обучается на искусственно созданных данных (например, на симуляции поведения пользователей). Она предсказывает, какой элемент интерфейса должен быть первым, вторым и т.д., чтобы пользователю было удобнее.

Пример:
Если модель "поняла", что пользователи чаще кликают на красные кнопки, она может вынести красную кнопку на первое место в списке.

6. Назначение приоритетов на основе характеристик

Что это?
Модель анализирует свойства элементов (цвет, размер, текст, положение) и поведение пользователей, чтобы решить, какие элементы важнее и должны быть выше или заметнее.

Пример:
Если кнопка "Купить" крупнее и ярче, а пользователи чаще на неё кликают, модель может повысить её приоритет и показать выше остальных.


Серверные операции


1.
Серверы работают на локальных портах

Что это значит?
Каждый сервер (программа, которая обрабатывает запросы) "слушает" определённый номер порта на вашем компьютере. Порт — это как дверь в доме: чтобы получить доступ к серверу, нужно стучаться именно в эту дверь.

Примеры:

  • Rocket на 8000 — если вы запустили сервер Rocket, он будет доступен по адресу http://localhost:8000.
  • Actix на 8081 — сервер Actix будет доступен по адресу http://localhost:8081.
  • Warp на 3030 — сервер Warp — по адресу http://localhost:3030.

Зачем это нужно?
Чтобы на одном компьютере могли одновременно работать несколько серверов, не мешая друг другу.

2. Запускаются в отдельных потоках ОС

Что это значит?
Поток (thread) — это как отдельный работник, который выполняет свою задачу параллельно с другими. Операционная система (ОС) управляет этими работниками.

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

Зачем это нужно?
Чтобы серверы могли работать одновременно и не блокировать друг друга.

3. Однопоточная среда выполнения Tokio

Что это значит?
Tokio — это библиотека для асинхронного программирования на языке Rust. "Однопоточная среда выполнения" означает, что Tokio управляет всеми асинхронными задачами в одном потоке, но делает это так быстро и эффективно, что кажется, будто задачи выполняются параллельно.

Пример:
Представьте, что у вас один очень быстрый повар (Tokio), который успевает готовить несколько блюд одновременно, переключаясь между ними так быстро, что создаётся впечатление параллельной работы.

Зачем это нужно?
Чтобы не тратить ресурсы на создание множества потоков, но при этом эффективно обрабатывать много задач.

4. Обработка асинхронных задач без пересечения границ потоков

Что это значит?
Асинхронные задачи — это задачи, которые могут ждать (например, ответ от базы данных или запрос к другому серверу), но не блокируют выполнение других задач. "Без пересечения границ потоков" означает, что все задачи выполняются внутри одного потока Tokio, без передачи между разными потоками.

Пример:
Представьте, что повар (Tokio) ставит кастрюлю на огонь и, пока она греется, начинает резать овощи. Он не передаёт работу другому повару (потоку), а делает всё сам, но очень эффективно.

Зачем это нужно?
Чтобы избежать сложностей с синхронизацией данных между потоками и ускорить выполнение задач.

Персонализация с помощью машинного обучения


Модель линейной регрессии

Что это?
Это простой алгоритм машинного обучения, который ищет зависимость между входными данными (например, возраст пользователя) и выходными (например, порядок элементов на экране). Он строит прямую линию, которая лучше всего описывает эту зависимость.

Пример:
Допустим, модель узнала, что пользователи до 30 лет чаще кликают на яркие картинки, а старше 30 — на текстовые блоки. Тогда для 25-летнего пользователя она может поставить картинки выше, а для 40-летнего — текст.

Вектор признаков

Что это?
Это набор числовых данных, которые описывают пользователя или ситуацию. В примере — это возраст (25.0) и предпочтение крупного текста (1.0).

Пример:
Если у вас возраст 25 лет и вы любите крупный текст, ваш вектор признаков может выглядеть так: [25.0, 1.0]. Если бы вы предпочитали мелкий текст, второе число было бы 0.0.

Прогнозирование значения и сопоставление с индексом элемента

Что это?
Модель по вектору признаков рассчитывает число (например, 3.7), а затем сопоставляет его с порядковым номером элемента интерфейса (например, 3.7 соответствует карточке с новостями).

Пример:
Если модель предсказала значение 2.1, а в списке элементов интерфейса под номером 2 — блок с видео, то этот блок будет показан выше остальных.

Итоговый пример

Представьте, что вы заходите в приложение:

  • Ваш возраст: 25 лет
  • Вы любите крупный текст
    Модель берёт ваши данные [25.0, 1.0], рассчитывает, что вам больше подойдёт интерфейс с крупными заголовками и видео на первом месте, и сервер отправляет приложению команду: «Покажи сначала видео, потом текст».


Зависимости и импорт

1. #[macro_use] extern crate rocket;

Что это?
Это строка подключает фреймворк
Rocket для создания веб-приложений на языке Rust. Ключевое слово extern crate говорит компилятору: "Используй библиотеку Rocket". А #[macro_use] позволяет использовать специальные макросы Rocket — это такие "сокращения" для быстрого написания кода, например, для определения маршрутов (адресов страниц).

2. use rocket_dyn_templates::{Template, context};

Что это?
Это подключает возможность работы с
динамическими шаблонами (например, HTML-страницами, которые меняются в зависимости от данных). Template — это шаблон страницы, а context — данные, которые в него подставляются.

3. use actix_web::{web, App, HttpServer, HttpResponse, Responder};

Что это?
Это подключает фреймворк
Actix-web — ещё один популярный инструмент для создания веб-серверов на Rust. Здесь:

  • App — основное приложение.
  • HttpServer — сервер, который слушает запросы.
  • HttpResponse — ответ сервера (например, "OK" или "Ошибка").
  • Responder — трейт, который помогает формировать ответы.

4. use warp::Filter; warp::ws::{WebSocket, Message};

Что это?
Это подключает фреймворк
Warp — ещё один инструмент для создания веб-серверов, но с акцентом на композицию фильтров (гибкое объединение правил обработки запросов). А WebSocket — это протокол для обмена сообщениями в реальном времени (например, чат).

5. futures::{SinkExt, StreamExt};

Что это?
Это утилиты для работы с
асинхронными потоками данных (например, при обмене сообщениями по WebSocket). SinkExt и StreamExt помогают отправлять и получать данные асинхронно.

6. std::collections::HashMap;

Что это?
Это стандартная структура данных —
хеш-таблица (словарь), где данные хранятся в виде пар "ключ-значение". Часто используется для кэширования (быстрого доступа к данным).

7. serde::{Deserialize, Serialize};

Что это?
Библиотека
Serde позволяет преобразовывать данные в/из формата JSON (или других). Serialize — для преобразования в JSON, Deserialize — для обратного процесса.

8. anyhow::Result;

Что это?
Библиотека
anyhow упрощает обработку ошибок. Result — это тип, который может содержать либо успешный результат, либо ошибку. anyhow позволяет легко создавать ошибки с описанием.

9. linfa::prelude::*; linfa_linear::{LinearRegression, FittedLinearRegression};

Что это?
Библиотека
Linfa — это набор инструментов для машинного обучения на Rust. Здесь подключается модуль для линейной регрессии (предсказание числовых значений по данным).

Рендеринг на стороне сервера

1. ndarray::{Array1, Array2};

Что это?
Это библиотека для работы с многомерными массивами (например, векторами и матрицами) в языке Rust. Часто используется в машинном обучении для хранения и обработки данных.

Простыми словами:
Представьте, что у вас есть таблица чисел (например, данные о погоде за месяц). Array1 — это строка или столбец (одномерный массив), а Array2 — это вся таблица (двумерный массив).

2. reqwest::Client;

Что это?
Это асинхронный HTTP-клиент для отправки запросов к API (например, для получения данных с сервера).

Простыми словами:
Представьте, что вы хотите узнать погоду на завтра. Вы отправляете запрос на сайт с прогнозом, а reqwest::Client помогает это сделать быстро и эффективно, не блокируя другие задачи.

3. tokio; std::thread; tokio::time::{sleep, Duration};

Что это?

  • tokio — это библиотека для асинхронного программирования в Rust (позволяет выполнять много задач одновременно, не дожидаясь завершения каждой).
  • std::thread — для работы с потоками (параллельное выполнение кода).
  • tokio::time::{sleep, Duration} — для работы со временем (например, задержки).

Простыми словами:
Представьте, что вы готовите завтрак: тосты, кофе и яичницу. tokio позволяет вам заниматься всем одновременно, а не ждать, пока поджарится тост, чтобы налить кофе. sleep — это как таймер, который говорит: "Подожди 5 минут, прежде чем проверять яичницу".

4. UIElement

Что это?
Это структура, которая описывает элемент интерфейса (например, кнопку, текст, контейнер).

Простыми словами:
Представьте, что вы рисуете страницу в телефоне: есть кнопка "Купить", текст "Акция!", иконка корзины. Каждый такой элемент — это UIElement.

5. UserData

Что это?
Структура, которая хранит информацию о пользователе (например, возраст и настройки).

Простыми словами:
Представьте, что вы заполняете анкету: вам 25 лет, и вы любите тёмную тему. Всё это хранится в UserData.

6. UIDescription

Что это?
Структура, которая объединяет элементы интерфейса (Vec<UIElement>) и данные пользователя (UserData) для формирования ответа API.

Простыми словами:
Представьте, что вы отправляете письмо: в конверт кладете листок с текстом (элементы интерфейса) и фотографию (данные пользователя). UIDescription — это конверт, который всё это упаковывает и отправляет на сервер.


API

1. Rocket: обработка шаблонных маршрутов

Что это?
Rocket — это фреймворк для создания веб-приложений на языке Rust. Он позволяет легко определять, какие страницы и данные показывать пользователю, когда тот заходит на сайт.

Ключевые моменты:

  • #[get("/")] — это аннотация, которая говорит: "Когда пользователь заходит на главную страницу сайта (/), выполни эту функцию".
  • fn index() -> Template — функция, которая возвращает шаблон страницы (например, index.hbs). Шаблон — это заготовка HTML-кода, в которую можно подставить данные (например, текст приветствия).
  • Template::render — механизм, который берёт шаблон, подставляет в него данные (например, message: "Привет!") и отдаёт пользователю готовую HTML-страницу.

Пример:
Представь, что у тебя есть сайт-визитка. Когда кто-то заходит на главную страницу, Rocket показывает шаблон с надписью "Добро пожаловать!" и кнопкой "Узнать больше". Шаблон лежит в папке templates/ и называется index.hbs.

2. Actix: API для данных пользовательского интерфейса

Что это?
Actix — ещё один фреймворк для Rust, который помогает создавать быстрые и асинхронные веб-приложения. Здесь он используется для создания API — интерфейса, через который фронтенд (то, что видит пользователь) получает данные.

Ключевые моменты:

  • async fn api_handler() — асинхронная функция, которая быстро отвечает на запросы, не блокируя работу сервера.
  • UIDescription — объект, который описывает, что должно появиться на экране (например, текст и кнопка).
  • HttpResponse::Ok().json(ui_desc) — сервер возвращает данные в формате JSON, которые фронтенд использует для отображения элементов.

Пример:
Допустим, у тебя есть мобильное приложение. Когда пользователь открывает главную страницу, приложение спрашивает у сервера: "Что тут показать?" Сервер на Actix отвечает: "Покажи текст 'Привет, мир!' и кнопку 'Нажми меня'".

3. Warp: обновления в реальном времени (WebSocket)

Что это?
Warp — это библиотека для Rust, которая позволяет организовывать обмен данными между сервером и клиентом в реальном времени (например, чат или живые уведомления).

Ключевые моменты:

  • async fn handle_websocket — функция, которая обрабатывает соединение по протоколу WebSocket.
  • websocket.next() — сервер ждёт сообщения от клиента, а когда получает — сразу отправляет его обратно (эхо-сервер).
  • Потоковая передача — данные передаются непрерывно, без перезагрузки страницы.

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

Итоговая картина:

  • Rocket — показывает страницы (рендерит HTML).
  • Actix — отдаёт данные для динамического интерфейса (JSON).
  • Warp — обеспечивает живую связь между сервером и клиентом (WebSocket).

Если представить сайт как ресторан:

  • Rocket — это официант, который приносит меню (страницу).
  • Actix — это повар, который готовит блюда (данные для интерфейса).
  • Warp — это бармен, который сразу наливает напитки по запросу (обновления в реальном времени).


ML

1. fn validate_element(element: &UIElement) -> Result<()>

Что это?
Функция, которая проверяет, правильно ли заполнен элемент интерфейса (например, кнопка или текст).

Простыми словами:
Представьте, что вы заполняете анкету. Если в поле "Имя" ничего не написано, программа скажет: "Ошибка! Имя обязательно". Так же и здесь: функция проверяет, что у каждого элемента интерфейса есть всё необходимое.

Примеры:

  • Для текста обязательно наличие содержимого (например, "Привет!").
  • Для кнопки обязательны метка (например, "Нажать") и действие (например, "Отправить форму").

Как работает?

  • Если что-то не так — возвращает ошибку с описанием (например, "У кнопки нет метки!").
  • Использует библиотеку anyhow для удобного создания ошибок.

2. fn predict_element_order(model: &FittedLinearRegression<f64>, ... ) -> Vec<usize>

Что это?
Функция, которая предсказывает, в каком порядке лучше показать элементы на экране, используя машинное обучение.

Простыми словами:
Представьте, что у вас есть список товаров, и вы хотите показать сначала те, которые пользователю нравятся больше. Функция анализирует данные (например, возраст пользователя и его предпочтения) и решает, в каком порядке показать элементы.

Как работает?

  1. Готовит данные:Собирает признаки (например, возраст пользователя и предпочитает ли он крупный текст).
    Например: [25, 1.0] — пользователю 25 лет, и он любит крупный текст.
  2. Использует модель:Модель (FittedLinearRegression) — это как умный калькулятор, который по данным предсказывает число.
    Например, модель может сказать: "Начинай показывать элементы с индекса 3".
  3. Переупорядочивает элементы:Берёт список индексов (например, [0, 1, 2, 3, 4]) и переставляет их, начиная с предсказанного индекса.
    Например, если модель сказала "3", то новый порядок: [3, 4, 0, 1, 2].

Зачем это нужно?
Чтобы интерфейс был удобнее для каждого пользователя — например, сначала показывать то, что ему интереснее.



Генерация и извлечение пользовательского интерфейса

1. fn generate_ui(elements: &[UIElement], order: &[usize], indent: usize) -> Result<()>

Что делает:
Эта функция рисует (генерирует) пользовательский интерфейс на экране.

Как работает:

  • elements — список всех элементов интерфейса (кнопки, текст, картинки и т.д.).
  • order — порядок, в котором нужно показать эти элементы (например, сначала кнопка, потом текст).
  • indent — отступ (сколько пробелов или пикселей оставить перед элементом, чтобы интерфейс выглядел аккуратно).

Пример:
Допустим, у вас есть два элемента:

  • Текст: "Привет, мир!"
  • Кнопка: "Нажми меня"

Если order = [1, 0], то сначала покажется кнопка, потом текст, с отступом в 4 пробела.

2. async fn fetch_ui_description(client: &Client, url: &str) -> Result<UIDescription>

Что делает:
Эта функция скачивает описание интерфейса из интернета.

Как работает:

  • client — программа, которая умеет отправлять запросы в интернет (например, браузер).
  • url — адрес, откуда скачивать описание (например, https://api.example.com/ui).
  • UIDescription — структура, в которой хранится, как должен выглядеть интерфейс (какие элементы, где расположены, какие стили и т.д.).

Пример:
Вы заходите на сайт, и браузер отправляет запрос на сервер, чтобы узнать, как должна выглядеть главная страница. Сервер присылает JSON, который функция преобразует в удобный формат.

3. async fn process_ui(...) -> Result<()>

Что делает:
Эта функция управляет всем процессом: от получения данных до показа интерфейса пользователю.

Как работает:

  • Извлекает данные — получает описание интерфейса (например, из интернета или базы данных).
  • Проверяет каждый элемент — убеждается, что все элементы корректны и их можно показать.
  • Проверяет кеш — смотрит, не сохраняли ли мы уже порядок элементов для этого пользователя (по user_id), чтобы не делать лишнюю работу.
  • Прогнозирует порядок — если порядок не сохранён, использует машинное обучение, чтобы предсказать, в каком порядке лучше показать элементы.
  • Генерирует интерфейс — вызывает generate_ui, чтобы нарисовать всё на экране.
  • Кэширует результаты — сохраняет порядок элементов, чтобы в следующий раз показать интерфейс быстрее.

Пример:
Вы заходите в приложение. Оно проверяет, не сохраняли ли вы раньше, как вам удобнее расположены кнопки. Если нет — предсказывает оптимальный порядок, рисует интерфейс и сохраняет его, чтобы в следующий раз не думать заново.



Функции запуска сервера


async fn — асинхронная функция

Что это?
Это функция, которая может выполняться не блокируя основной поток программы. Она позволяет запускать долгие операции (например, запросы к базе данных, сетевые запросы) "в фоне", пока программа продолжает работать.

Пример:
Представьте, что вы заказали еду на вынос и продолжаете смотреть фильм, пока её готовят. Вы не стоите у плиты и не ждёте — вы занимаетесь другими делами, а когда еда готова, вам приносят уведомление.

Result<(), rocket::Error>

Что это?
Это тип данных в Rust, который означает, что функция может завершиться либо успешно (тогда возвращается Ok(())), либо с ошибкой (тогда возвращается Err(rocket::Error)).

Пример:
Представьте, что вы пытаетесь открыть дверь ключом. Либо дверь откроется (успех), либо ключ сломается (ошибка).

маршрут API

Что это?
Это адрес, по которому можно получить или отправить данные. Например, /api/users может возвращать список пользователей.

Пример:
Если вы заказываете такси через приложение, то приложение отправляет запрос на маршрут /api/order, чтобы создать заказ.

запускает асинхронно

Что это?
Значит, что сервер не блокирует выполнение программы, а работает в фоне, обрабатывая запросы по мере их поступления.

Пример:
Вы можете слушать музыку и одновременно писать сообщение — оба процесса идут параллельно.