В этой статье я поделюсь своим опытом создания рабочего процесса (workflow) в n8n, который позволяет автоматически генерировать изображения на моём сервере, используя локально размещённые модели (ComfyUI и Gemma3). Это отличный способ оптимизировать работу и использовать свои ресурсы максимально эффективно!
Что понадобится:
- Сервер/Рабочая станция: где будет работать вся наша система (сервер с Linux).
- Генератор изображений:
- ComfyUI: (рекомендуется из-за простого API и гибкости графа узлов).
- n8n (Workflow Automation Tool): устанавливаем локально (например, через Docker).
- Модели: например, Stable Diffusion Turbo или Flux.
Теперь давай детально разберём каждую ноду (или ещё называют узел) моего workflow в n8n.
Рабочий процесс состоит из 9 узлов, которые последовательно выполняют задачи: от генерации промпта до загрузки готовой картинки.
1. ⚡️ Schedule Trigger (триггер по расписанию)
Это стартовая точка всего рабочего процесса. Он отвечает за запуск автоматизации в заданное время или с определенной периодичностью (в моём случае задан интервал 1 раз в 10 минут). Это идеальный вариант для регулярного контента (например, «Картинка дня» для блога) и для полной автономности.
2. 🌐 Нода Ollama_Gemma_Prompt (генерация промпта)
Использует локально запущенную большую языковую модель (LLM) Gemma3:12b через Ollama для
создания текста, который станет входным промптом для генерации изображения.
- Тип узла: HTTP Request
- Метод: POST
- URL: http://192.168.0.240:11434/api/generate
- Аутентификация: none
- Send Headers:
- Name: Content-Type
- Value: application/json
- Send Body:
- Body Content Type: JSON
- Specify Body: Using JSON
- JSON
{
"model": "gemma3:12b",
"prompt": "Твоя задача — действовать как генератор промптов. Всегда используй эстетику, вдохновленную Поп-артом, Мемфисом и Абстрактным стилем. Создай ОДИН промпт для FLUX, состоящий из 3-5 предложений. Промпт должен быть предназначен для генерации бесшовного паттерна. Обязательно включи: 'seamless tile pattern', смелую, контрастную цветовую палитру (указывай 3-4 цвета) и четкое описание абстрактных, волнообразных или геометрических форм. Выдай ТОЛЬКО сам промпт, без заголовков, объяснений, вопросов и форматирования Markdown (без жирного шрифта, звездочек и т.п.)**",
"stream": false,
"options": {
"temperature": 0.7
}
}
Это делает процесс уникальным — изображения генерируются не просто по-статичному промпту, а по промпту, сгенерированному ИИ!
3. 📝 Clean_Prompt (очистка/фильтрация промпта)
Обработка и форматирование текста (конечная очистка на всякий случай, чтобы исключить попадание любых нежелательных символов), полученного от LLM. API ComfyUI требует чистый текст без лишних символов (иначе вы можете столкнуться с ошибками при обработке json-запросов в дальнейшем).
Нода называется Edit Fields (Set).
- Mode: Manual Mapping
- Первый параметр:
- Имя: safe_prompt
- Значение:
{{ JSON.stringify($node['Ollama_Gemma_Prompt'].json.response).slice(1, -1).replace(/\\n/g, ' ').replace(/"/g, '\\"') }}
- Второй параметр:
- Имя: new_seed (это поле нам понадобится для генерации случайного числа, которое будем передавать в json ComfyUI, чтобы генерировались разные картинки)
- Значение:
{{ Math.floor(Math.random() * 1000000000000) }}
4. 🌐 ComfyUI_Send_Workflow (отправка рабочего процесса генерации)
Отправка готового JSON-пэйлоада с промптом в ComfyUI для начала генерации. Немного отходя от
процесса объясню, где взять шаблон json файла, который ожидает ComfyUI. Для его получения необходимо войти в ComfyUI, загрузить шаблон, который вы хотите использовать (я выбрала flux_dev_checkpoint_example, его можно найти в поиске шаблонов). Когда шаблон выбран, в верхнем меню нажимаем на иконку ComfyUI -> File -> Export API. И скачиваем готовый json. В дальнейшем мы будем использовать именно его, заменяя некоторые параметры.
- Тип узла: HTTP Request
- Метод: POST
- URL: http://192.168.0.240:8188/prompt
- Send Headers:
- Name: Content-Type
- Value: application/json
- Send Body
- Body Content Type: JSON
- Specify Body: Using JSON
- JSON:
{
"prompt": {
"6": {
"inputs": {
"text": "{{ $node['Clean_Prompt'].json.safe_prompt }}",
"clip": [
"30",
1
]
},
"class_type": "CLIPTextEncode",
"_meta": {
"title": "CLIP Text Encode (Positive Prompt)"
}
},
"8": {
"inputs": {
"samples": [
"31",
0
],
"vae": [
"30",
2
]
},
"class_type": "VAEDecode",
"_meta": {
"title": "VAE Decode"
}
},
"9": {
"inputs": {
"filename_prefix": "{{ $node['Clean_Prompt'].json.new_seed }}",
"images": [
"8",
0
]
},
"class_type": "SaveImage",
"_meta": {
"title": "Save Image"
}
},
"27": {
"inputs": {
"width": 1024,
"height": 1024,
"batch_size": 1
},
"class_type": "EmptySD3LatentImage",
"_meta": {
"title": "EmptySD3LatentImage"
}
},
"30": {
"inputs": {
"ckpt_name": "flux1-dev-fp8.safetensors"
},
"class_type": "CheckpointLoaderSimple",
"_meta": {
"title": "Load Checkpoint"
}
},
"31": {
"inputs": {
"seed": {{ $json.new_seed }},
"steps": 30,
"cfg": 3.5,
"sampler_name": "dpmpp_3m_sde",
"scheduler": "simple",
"denoise": 1,
"model": [
"30",
0
],
"positive": [
"35",
0
],
"negative": [
"33",
0
],
"latent_image": [
"27",
0
]
},
"class_type": "KSampler",
"_meta": {
"title": "KSampler"
}
},
"33": {
"inputs": {
"text": "3D, depth of field, shadow, low resolution, watermark, text, texturing, border, frames, ugly, soft focus, blurry, out of focus, cropped.\n",
"clip": [
"30",
1
]
},
"class_type": "CLIPTextEncode",
"_meta": {
"title": "CLIP Text Encode (Negative Prompt)"
}
},
"35": {
"inputs": {
"guidance": 3.5,
"conditioning": [
"6",
0
]
},
"class_type": "FluxGuidance",
"_meta": {
"title": "FluxGuidance"
}
}
}
}
В этот JSON (в Body) мы динамически вставляем очищенный промпт из узла Clean_Prompt и значение для ‘seed’. Результат: Узел возвращает ID генерации (Prompt ID), который нам понадобится для проверки статуса в следующих шагах.
5. ⏸️ Wait (Ожидание)
Приостановка выполнения workflow, пока генерация изображения не завершится. В моём случае (с моим железом), генерация занимала максимум 3 минуты, поэтому я поставила задержку на 350 секунд.
- Resume: After Time Interval
- Wait Amount: 350.00
- Wait Unit: Seconds
6. 🌐 ComfyUI_Get_History1 (проверка статуса и получение истории)
После паузы (когда отработает свой временной интервал предыдущая нода), этот узел проверяет, что генерация завершена, и получает данные истории.
- Тип узла: HTTP Request
- Метод: GET
- URL: http://192.168.0.240:8188/history
- Send Query Parameters: true
- Specify Query Parameters: Using Fields Below
- Name: prompt_id
- Value:
{{ $node['ComfyUI_Send_Workflow'].json.prompt_id }}(мы динамически вставляем ID, чтобы получить информацию только о нашей текущей задаче)
Вкладка Settings:
- Retry on Fail: true
- Max. Tries: 5
- Wait Between Tries (ms): 1500
- On Error: Continue (using error output)
- Notes: 404 (тут мы указываем какая ошибка нас интересует)
Обработка ошибок тут ключевой момент. И мы используем продвинутые возможности n8n для обработки исключений.
- Сценарий успеха (Success): Если генерация прошла успешно, ComfyUI возвращает данные истории с HTTP-статусом 200 OK. n8n обрабатывает этот ответ, и рабочий процесс автоматически переходит к следующему узлу, подключенному к ветке Success (в нашем случае, это ComfyUI_Get_History).
- Сценарий ошибки (Error): Если генерация не успела завершиться или ComfyUI не находит историю по указанному ID, он может вернуть HTTP-статус 404 Not Found.
7. 🌐 ComfyUI_Get_History (получение имени файла)
Из данных истории, полученных на предыдущем шаге, извлекаем точное имя сгенерированного файла (например, ComfyUI_00001_.png). Это критический шаг, так как имя файла уникально и нужно для его скачивания.
- Тип узла: HTTP Request
- Метод: GET
- URL: http://192.168.0.240:8188/history
- Send Query Parameters: true
- Specify Query Parameters: Using Fields Below
- Name: prompt_id
- Value:
{{ $node['ComfyUI_Send_Workflow'].json.prompt_id }}
8. 🌐 ComfyUI_Get_Image (скачивание изображения)
Запрос к API ComfyUI для скачивания файла.
- Тип узла: HTTP Request
- Метод: GET
- URL: http://192.168.0.240:8188/view
- Send Query Parameters: true
- Specify Query Parameters: Using Fields Below
- Name: filename
- Value:
{{ Object.values($json)[Object.values($json).length - 1].outputs['9'].images[0].filename }}
- Response Format: Должен быть настроен на получение бинарных данных (Binary или File).
- Options -> Response
- Response Format: File
- Put Output in Field: data
9. 🗄️ FTP (загрузка файла)
Последний шаг — сохранение сгенерированного файла на внешний ресурс или в хранилище. Тут можно использовать разные ноды, ФТП это одна из них, так же можно подключить сохранение в Гугл Диск или другие места хранения.
- Тип узла: Специализированный узел FTP
- Действие: Upload
- Path:
/home/user/n8n_patterns_storage/{{ $node['ComfyUI_Get_History'].json[Object.keys($node['ComfyUI_Get_History'].json)[0]].outputs['9'].images[0].filename.replace(/\.([^.]+)$/, '_' + Date.now() + '.$1') }}тут ещё я добавляю штамп времени к имени файла
В статье подробно описан автоматизированный рабочий процесс (workflow) в n8n для генерации уникальных изображений на собственном сервере. Этот процесс использует локальную языковую модель Ollama (Gemma) для динамического создания текстовых промптов, которые затем передаются через API в ComfyUI, работающий на сервере с Linux и Docker, для выполнения фактической генерации изображений. Ключевые этапы включают запуск по расписанию, очистку промпта, отправку запроса к ComfyUI, ожидание завершения и, что важно, обработку ошибок (404 Not Found) через ветвление workflow. Завершается процесс скачиванием готового изображения и его автоматической загрузкой на удалённый FTP-сервер, демонстрируя полностью замкнутый и автономный цикл создания контента, который позволяет эффективно использовать локальные ресурсы и не зависеть от облачных сервисов.
В следующей статье, я расскажу как установить n8n на свой локальный (и не локлаьный) сервер на Linux с использованием Docker Compose.






