Серьезно? Веб-приложение в одном Python-файле?
Да. И это работает лучше, чем вы думаете. Gradio 6, вышедший в конце 2024 года, тихо добавил gr.HTML — компонент, который меняет правила игры. Больше не нужен React, Vue, Webpack, npm install или даже базовые знания JavaScript.
gr.HTML теперь поддерживает scoped CSS, кастомные JavaScript события и даже асинхронные обновления DOM. Это уже не просто «вставка HTML», а полноценный фреймворк внутри фреймворка.Что умеет gr.HTML в 2026 году
Если вы все еще думаете, что это просто <div>ваш html</div>, вы отстали на два года.
- Scoped CSS — стили не утекают за пределы компонента. Пишите как в Vue SFC, но без сборки
- JavaScript события —
js_on_load,js_on_change, полный доступ к DOM API - Двусторонняя связь — обновляйте HTML из Python, ловите события из браузера
- Кастомные шаблоны — загружайте HTML из файлов, генерируйте через Jinja2
- Поддержка современных CSS — Grid, Flexbox, CSS Variables, даже CSS-in-JS если захотите
Почему это убивает React для AI-прототипов
Создаете демо для новой модели? Пока фронтендер настраивает сборку, вы уже развернули приложение на Hugging Face Spaces.
| React/Next.js | Gradio + gr.HTML |
|---|---|
| node_modules (200MB+) | requirements.txt (одна строка) |
| Webpack/Vite конфиг | Никакого конфига |
| Деплой на Vercel | gradio deploy или HF Spaces |
| Горячая перезагрузка через плагины | Встроенная горячая перезагрузка |
Не поймите меня неправильно — для масштабных продуктов React все еще король. Но для прототипов, демо, внутренних инструментов и AI-интерфейсов? Это overkill уровня «убить комара кувалдой».
Сравнение не совсем честное — React делает больше. Но в 95% случаев AI-разработчикам не нужны эти «больше». Им нужен работающий интерфейс сегодня, а не через неделю.
Реальные примеры, а не «hello world»
1 Pomodoro Timer с CSS анимациями
Таймер помодоро — идеальный тест. Нужны: анимации (CSS keyframes), таймер (setInterval), управление состоянием (старт/пауза/сброс). В React это компонент на 100 строк. В Gradio — 50 строк Python и HTML вместе.
Секрет в js_on_load:
import gradio as gr
from datetime import datetime
def create_timer():
html = """
<style>
@keyframes pulse {
0% { opacity: 1; }
50% { opacity: 0.7; }
100% { opacity: 1; }
}
.timer-active { animation: pulse 2s infinite; }
</style>
<div id="timer-container" class="p-6 rounded-xl bg-gradient-to-r from-blue-50 to-indigo-50">
<div id="time-display" class="text-5xl font-mono text-center mb-4">25:00</div>
<div class="flex gap-2 justify-center">
<button onclick="startTimer()" class="px-4 py-2 bg-green-500 text-white rounded-lg">Старт</button>
<button onclick="pauseTimer()" class="px-4 py-2 bg-yellow-500 text-white rounded-lg">Пауза</button>
<button onclick="resetTimer()" class="px-4 py-2 bg-red-500 text-white rounded-lg">Сброс</button>
</div>
</div>
<script>
let timerInterval;
let timeLeft = 25 * 60;
function startTimer() {
document.getElementById('timer-container').classList.add('timer-active');
timerInterval = setInterval(updateTimer, 1000);
}
function updateTimer() {
if (timeLeft <= 0) {
clearInterval(timerInterval);
alert('Время вышло!');
return;
}
timeLeft--;
const minutes = Math.floor(timeLeft / 60);
const seconds = timeLeft % 60;
document.getElementById('time-display').textContent =
`${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
}
</script>
"""
return gr.HTML(html)
with gr.Blocks() as demo:
create_timer()
demo.launch()
Видите? Никакого состояния в Python, весь UI живет в браузере. Но при этом мы можем в любой момент отправить данные в Python — например, логировать сессии или интегрировать с AI-ассистентом.
2 Kanban Board с drag-and-drop
«Это точно потребует React», — скажете вы. А вот и нет. HTML5 Drag and Drop API работает без библиотек.
Хитрость в том, чтобы использовать gr.on для обработки событий:
import json
import gradio as gr
def kanban_app():
html = """
<style>
.column {
min-height: 400px;
background: #f8fafc;
border-radius: 12px;
padding: 16px;
}
.task {
background: white;
padding: 12px;
margin: 8px 0;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
cursor: move;
}
.task:hover {
box-shadow: 0 4px 8px rgba(0,0,0,0.15);
}
</style>
<div class="grid grid-cols-3 gap-6 p-6">
<div class="column" id="todo" ondrop="drop(event)" ondragover="allowDrop(event)">
<h3 class="text-lg font-bold mb-4">To Do</h3>
<div class="task" draggable="true" id="task1" ondragstart="drag(event)">Написать код</div>
</div>
<div class="column" id="progress" ondrop="drop(event)" ondragover="allowDrop(event)">
<h3 class="text-lg font-bold mb-4">In Progress</h3>
</div>
<div class="column" id="done" ondrop="drop(event)" ondragover="allowDrop(event)">
<h3 class="text-lg font-bold mb-4">Done</h3>
</div>
</div>
<script>
function allowDrop(ev) {
ev.preventDefault();
}
function drag(ev) {
ev.dataTransfer.setData("text", ev.target.id);
}
function drop(ev) {
ev.preventDefault();
const data = ev.dataTransfer.getData("text");
const task = document.getElementById(data);
ev.target.appendChild(task);
// Отправляем обновление в Python
const boardState = {
todo: Array.from(document.getElementById('todo').children)
.filter(el => el.classList.contains('task'))
.map(el => el.id),
progress: Array.from(document.getElementById('progress').children)
.filter(el => el.classList.contains('task'))
.map(el => el.id),
done: Array.from(document.getElementById('done').children)
.filter(el => el.classList.contains('task'))
.map(el => el.id)
};
// Магия Gradio - вызываем Python функцию
updateBoard(boardState);
}
</script>
"""
return gr.HTML(html)
def save_board_state(state_json):
state = json.loads(state_json)
print(f"Board updated: {state}")
# Здесь можно сохранить в БД или отправить куда-то
return "Состояние сохранено"
with gr.Blocks() as demo:
html_component = kanban_app()
output = gr.Textbox(label="Статус")
# Связываем JavaScript с Python
html_component.js_on(
"updateBoard", # имя события из JS
save_board_state, # Python функция
output # куда вывести результат
)
demo.launch()
Это тот самый момент, когда понимаешь — границы между фронтендом и бэкендом стираются. JavaScript дергает Python функции, Python может обновлять DOM. Все в одном файле.
Когда использовать (а когда нет)
gr.HTML не серебряная пуля. Вот когда он сияет:
- AI-демо — показать работу модели с кастомной визуализацией
- Внутренние инструменты — админки, дашборды, утилиты для команды
- Быстрые прототипы — проверить идею до привлечения фронтендера
- Образовательные проекты — студенты учатся логике, а не сборке
- Микросервисы с UI — каждый сервис со своим минимальным интерфейсом
А вот когда бежать прочь:
- Мобильное приложение (хотя PWA возможно)
- Комплексный SPA с роутингом на 10+ страниц
- Высоконагруженный публичный сервис
- Когда в команде уже есть фронтендеры (не обижайте коллег)
Под капотом: как это вообще работает
Gradio 6 использует два механизма для gr.HTML:
- Shadow DOM для изоляции — ваши стили не конфликтуют с другими компонентами
- Message passing через WebSockets — JavaScript ↔ Python общение
- Динамическая перезагрузка — меняете код, интерфейс обновляется без перезагрузки страницы
С февраля 2025 года Gradio поддерживает gr.CustomComponent — можно создавать переиспользуемые компоненты на основе gr.HTML и публиковать их как pip-пакеты. Эко-система растет.
Интеграция с AI-ассистентами
Вот где начинается магия. Представьте:
- Вы описываете интерфейс текстом Claude или ChatGPT
- AI генерирует HTML/CSS/JS (как в статье про Syntux)
- Вставляете в gr.HTML
- Запускаете — интерфейс работает
Не нужно знать фронтенд. Не нужно дебажить сборку. Не нужно ждать CI/CD. Это Gizmo, но для серьезных приложений.
Хотите копнуть глубже? В статье «Фронтенд без фронтендера» похожий подход, но там все еще нужна сборка. Здесь — вообще ничего.
Производительность: а не тормозит ли?
Справедливый вопрос. Ответ: зависит.
Для интерфейсов с десятками компонентов — работает как родной. Gradio 6 переписали на Preact (легковесный React), и рендеринг стал быстрее на 40%.
Проблемы начинаются, если вы пытаетесь вставить 10 000 строк таблицы в один gr.HTML. Но в этом случае проблема не в Gradio, а в архитектуре.
gr.Dataframe с виртуализацией. Для графов — gr.Plot. gr.HTML — для кастомных компонентов, которые нельзя сделать стандартными.Деплой: от локальной машины до облака за 5 минут
Вот почему я люблю Gradio:
# Локально
gradio app.py
# На Hugging Face Spaces (бесплатно)
gradio deploy --title "Мое AI-приложение"
# Своя инфраструктура
docker build -t my-app .
docker run -p 7860:7860 my-app
Hugging Face Spaces в 2026 году — это не просто хостинг. Это discovery platform, где люди находят ваши демо. Выложили крутое приложение? Получите звезды, форки, может даже контрибьюторов.
Ограничения и подводные камни
Без розовых очков:
- SEO — нет. Это SPA, Google не увидит контент
- Браузерная история — роутинг нужно писать вручную
- TypeScript — только JavaScript (хотя можно подключить через CDN)
- Hot reload для HTML — работает, но иногда нужно обновить страницу
- Отладка — DevTools показывают Shadow DOM, нужно привыкнуть
Но главное ограничение — ментальное. Вы привыкнете к простоте и будете раздражаться, когда придется возвращаться к «нормальной» фронтенд-разработке.
Что дальше? Будущее gr.HTML
По слухам (и по roadmap на GitHub):
- Генерация компонентов из скриншотов — загрузите макет Figma, получите gr.HTML
- Библиотека готовых компонентов — как MUI, но для Gradio
- Еще глубже интеграция с AI — «создай интерфейс для классификации текста» → готовое приложение
- Поддержка WebAssembly — выполняйте тяжелую логику в браузере
Уже сейчас можно подключить llama.cpp WebUI как компонент. Или встроить Qwen2.5 прямо в интерфейс.
Начните сегодня
Не нужно переписывать существующие проекты. Начните с малого:
- Возьмите простой интерфейс из вашего приложения
- Перепишите его на gr.HTML в отдельном файле
- Сравните сложность и скорость разработки
- Примите решение
Мой прогноз: к концу 2026 года 30% AI-демо на Hugging Face будут использовать gr.HTML для кастомных интерфейсов. Потому что когда есть молоток, который забивает все гвозди, зачем искать отвертку?
P.S. Если застряли — посмотрите исходный код популярных Spaces. В 90% случаев это один Python-файл. Без package.json. Без tsconfig. Без головной боли.