惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Microsoft Security Blog
Microsoft Security Blog
Apple Machine Learning Research
Apple Machine Learning Research
美团技术团队
WordPress大学
WordPress大学
酷 壳 – CoolShell
酷 壳 – CoolShell
G
Google Developers Blog
阮一峰的网络日志
阮一峰的网络日志
The Cloudflare Blog
J
Java Code Geeks
Martin Fowler
Martin Fowler
M
MIT News - Artificial intelligence
IT之家
IT之家
博客园 - 三生石上(FineUI控件)
月光博客
月光博客
Google DeepMind News
Google DeepMind News
小众软件
小众软件
V
V2EX
Hugging Face - Blog
Hugging Face - Blog
爱范儿
爱范儿
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
Jina AI
Jina AI
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
腾讯CDC
B
Blog

Все публикации подряд на Хабре

Ловим музу за клавиатуру: как айтишнику стать автором Что умеет Midjourney в 2026? Мой немного грустный разбор этого шикарного инструмента Никто не любит писать тесты, но ИИ может исправить это IPv8 выглядит как мечта. Поэтому почти наверняка не взлетит Производители вернули в продажу материнки с DDR3. Что происходит? Управление агентом с телефона через Telegram теперь в KodaCode От координации к лидерству: как меняется роль руководителя разработки Я сделала родителям бизнес вместо пенсии: зарабатываем 70 тысяч, мама не даёт продать В три раза быстрее приемка товара и оптимизация трудозатрат на 73%: как «РСТ-Инвент» помог Gulliver Group ИИ-шечный мир победил? О влиянии искусственного интеллекта на игропром Кремль снижает давление на Телеграмм пока Европа строит интернет по паспорту Как CEO, CTO и CIO за 8 часов собрали ИИ-директора, который умеет держать позицию под давлением Как (не) потерять домен за выходные Вместо 8 разных VPS: как я организовал практику студентам на одном сервере Почему твой Open Source проект не замечают? R&D: искусство управления неопределенностью в разработке AI-дефляция: вакансий для разработчиков больше, а рост зарплат — худший за 15 лет Мы отдали управление роботами OpenClaw. Что из этого вышло Галактический ID: система идентификации для всех форм разумной жизни Шесть основ бизнес-анализа: начинаем с вопроса «Кто в игре?» Код-ревью, в котором дело не в коде Данные переехали. Команда — нет Системной подход к сдаче OSWE в 2025 Почему комната управления реактором покрашена в цвет морской пены 4 YAML-файла вместо PySpark: как аналитикам строить пайплайны без разработчиков LLM-агент для поиска свободных доменов: автоматизируем подбор Когда, зачем и как правильно начинать новую сессию в Claude Code? Как я заставил нейросеть писать макросы для FreeCAD Анатомия ИИ‑агента для подбора персонала. От тысячи резюме к топ‑10 за минуты Опыт разработчика как экономика внимания
Как я сделал генератор палитр на Node.js + DeepSeek за дв...
Максим Константинов · 2026-06-23 · via Все публикации подряд на Хабре

Средний

4 мин

55

Как-то вечером я поймал себя на том, что трачу по 20 минут на поиск цветовой палитры для каждого нового проекта. Coolors, Adobe Color, случайные пины в Pinterest — и всё равно ощущение «не то». Тогда я задал себе вопрос: а что если вместо колеса оттенков начинать со слова? «Рассвет», «шторм», «лакшери» — у каждого слова есть интуитивный цвет.

Так появился Колорит — инструмент, который превращает слово или фотографию в цветовую палитру с помощью ИИ. В этой статье расскажу про технические решения, prompt engineering для DeepSeek и пару неочевидных браузерных API.

Стек и архитектура

Принципиально не хотел тащить тяжёлый фреймворк. Итоговый стек:

  • Бэкенд: Node.js + Express, ~150 строк

  • Фронтенд: чистый HTML/CSS/JS без сборщиков

  • ИИ: DeepSeek API (модель deepseek-chat)

  • Деплой: Linux VPS, pm2, nginx reverse proxy

Никакой базы данных — история запросов живёт в sessionStorage браузера. Поднялось за один вечер.

Режим «Слово → Цвет»: prompt engineering

Главная задача — получить от LLM строго структурированный ответ, а не поток текста. Я потратил время на правильный промпт и в итоге пришёл к такому подходу:

async function generatePalette(query, count) { const res = await fetch('https://api.deepseek.com/chat/completions', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${process.env.DEEPSEEK_API_KEY}` }, body: JSON.stringify({ model: 'deepseek-chat', temperature: 0.8, messages: [{ role: 'system', content: `Ты — эксперт по цвету и дизайну. Возвращай ТОЛЬКО валидный JSON-массив, без markdown, без пояснений. Формат каждого элемента: {"hex":"#RRGGBB","name_ru":"Название","name_en":"Name"}` }, { role: 'user', content: `Создай палитру из ${count} цветов для слова/фразы: "${query}". Цвета должны передавать настроение, ассоциации и смысл слова. Избегай слишком похожих оттенков.` }] }) }); const data = await res.json(); return JSON.parse(data.choices[0].message.content); }

Ключевые детали промпта:

  • System prompt жёстко задаёт формат — только JSON, никакого markdown

  • temperature: 0.8 — достаточно креативно, но предсказуемо по формату

  • Явная просьба «избегать похожих оттенков» — без неё модель иногда возвращала 5 оттенков одного цвета

Режим «Фото → Цвет»: кластеризация в браузере

Здесь я принципиально не хотел отправлять фото на сервер — и для приватности, и для скорости. Весь алгоритм работает через Canvas API:

async function generatePalette(query, count) { const res = await fetch('https://api.deepseek.com/chat/completions', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${process.env.DEEPSEEK_API_KEY}` }, body: JSON.stringify({ model: 'deepseek-chat', temperature: 0.8, messages: [{ role: 'system', content: `Ты — эксперт по цвету и дизайну. Возвращай ТОЛЬКО валидный JSON-массив, без markdown, без пояснений. Формат каждого элемента: {"hex":"#RRGGBB","name_ru":"Название","name_en":"Name"}` }, { role: 'user', content: `Создай палитру из ${count} цветов для слова/фразы: "${query}". Цвета должны передавать настроение, ассоциации и смысл слова. Избегай слишком похожих оттенков.` }] }) }); const data = await res.json(); return JSON.parse(data.choices[0].message.content); }

Для кластеризации использовал k-means в HSL-пространстве — оно лучше отражает перцептивное расстояние между цветами, чем RGB. После нескольких итераций центроиды стабилизируются и дают доминирующие цвета изображения.

Неочевидные детали

iOS: сохранение в Фото, а не в Загрузки

Стандартный download-атрибут на iPhone сохраняет файл в «Загрузки» — пользователи этого не ожидают. Решение — Web Share API:

async function saveOrShare(canvas) { canvas.toBlob(async (blob) => { const file = new File([blob], 'palette.png', { type: 'image/png' }); // iOS поддерживает share файлов — открывает нативный шаринг if (navigator.canShare?.({ files: [file] })) { await navigator.share({ files: [file], title: 'Палитра' }); } else { // Fallback для десктопа const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = 'palette.png'; a.click(); URL.revokeObjectURL(url); } }); }

На iOS появляется нативный Share Sheet — можно сохранить в Фото, отправить в Telegram, скопировать и т.д.

Тёмная тема и восприятие цвета

Неожиданная проблема: одна и та же палитра выглядит иначе на тёмном и светлом фоне из-за симультанного контраста. Оттенок, который кажется тёплым на тёмном фоне, на светлом может казаться холоднее. Решил это через CSS-переменные с отдельными значениями для каждой темы, плавный переход transition: background .35s сглаживает переключение.

История без базы данных

Последние 10 запросов хранятся в sessionStorage — исчезают при закрытии вкладки, никакой слежки, никакого сервера. Отображаются как кликабельные чипы под строкой поиска.

Что получилось

Инструмент работает на https://konstmax.ru/colorit/. Два режима — «Слово→Цвет» и «Фото→Цвет», переключатель RU/EN, тёмная и светлая темы, история запросов, экспорт PNG с нативным шарингом на iOS.

Основные цифры после запуска:

  • Время ответа DeepSeek: ~1.5–3 сек на запрос

  • Размер фронтенда без зависимостей: ~18 KB CSS + ~22 KB JS

  • Canvas-кластеризация 200×200px: ~40 мс в браузере

Что дальше: «Слово-система»

Колорит — первый модуль в задуманной экосистеме «Слово-система»: набора инструментов, где точкой входа является обычное слово или образ. Слово→Цвет уже есть. Следующие модули в разработке — Слово→Шрифт, Слово→Форма. Идея в том, чтобы ИИ помогал дизайнеру и разработчику на самом раннем этапе — когда есть только ощущение, но нет ещё ни одного пикселя.