feat: multi-select image styles, fix descriptions

- IMAGE_STYLES: исправлены описания (realistic-photo = AI-фотореализм, не сток)
- Стиль изображений: single-select → multi-select (чередуется случайно)
- Добавлено пояснение: AI-генерация ≠ стоковые фото; реальный человек → поиск фото
- DB: channel_style.image_style varchar(30) → varchar(255)
This commit is contained in:
Ник (Claude)
2026-06-10 15:50:49 +03:00
parent e330ac3871
commit 8244789f10
+21 -10
View File
@@ -40,12 +40,12 @@ const EMOJI = [
];
const IMAGE_STYLES = [
{ v: 'realistic-photo', label: 'Реалистичное фото', desc: 'Стоковая фотография' },
{ v: 'flat-illustration', label: 'Плоская иллюстрация', desc: 'Editorial vector' },
{ v: 'realistic-photo', label: 'Реалистичное фото', desc: 'AI-фотореализм, не сток' },
{ v: 'flat-illustration',label: 'Плоская иллюстрация', desc: 'Editorial vector' },
{ v: '3d-render', label: '3D рендер', desc: 'Pixar-like' },
{ v: 'cartoon', label: 'Мультяшный', desc: 'Comic book' },
{ v: 'minimal', label: 'Минимализм', desc: 'Один элемент' },
{ v: 'abstract', label: 'Абстракция', desc: 'Без объектов' },
{ v: 'abstract', label: 'Абстракция', desc: 'Геометрия, настроение' },
{ v: 'sketch', label: 'Скетч', desc: 'Карандашный рисунок' },
{ v: 'cyberpunk', label: 'Киберпанк', desc: 'Неон, будущее' },
];
@@ -91,7 +91,9 @@ export default function ChannelEdit({ channel }) {
// Картинки
const [imageEnabled, setImageEnabled] = useState(style.image_enabled ?? false);
const [imageStyle, setImageStyle] = useState(style.image_style || 'flat-illustration');
const [imageStyles, setImageStyles] = useState(
(style.image_style || 'flat-illustration').split(',').map(s => s.trim()).filter(Boolean)
);
const [imagePalette, setImagePalette] = useState(style.image_palette || 'auto');
const [imageCustomColors, setImageCustomColors] = useState(style.image_custom_colors || '');
const [imagePromptInstructions, setImagePromptInstructions] = useState(style.image_prompt_instructions || '');
@@ -127,7 +129,7 @@ export default function ChannelEdit({ channel }) {
banned_words: bannedWords.split(',').map(s => s.trim()).filter(Boolean),
banned_topics: bannedTopics.split(',').map(s => s.trim()).filter(Boolean),
image_enabled: imageEnabled,
image_style: imageStyle,
image_style: imageStyles.join(','),
image_palette: imagePalette,
image_custom_colors: imageCustomColors.trim() || null,
image_prompt_instructions: imagePromptInstructions.trim() || null,
@@ -350,22 +352,31 @@ export default function ChannelEdit({ channel }) {
{imageEnabled && (
<>
<div className="card p-5">
<h3 className="font-semibold text-sm mb-3 flex items-center gap-2">
<h3 className="font-semibold text-sm mb-1 flex items-center gap-2">
<ImageIcon className="w-4 h-4 text-accent" />
Стиль изображений
<span className="text-gray-500 font-normal">(можно несколько система будет чередовать)</span>
</h3>
<p className="text-xs text-gray-500 mb-3">
Все стили это <b>AI-генерация</b>, не стоковые фото.
Если в посте упоминается реальный человек система автоматически ищет его фото в интернете вместо генерации.
</p>
<div className="grid grid-cols-2 sm:grid-cols-4 gap-2">
{IMAGE_STYLES.map(s => (
{IMAGE_STYLES.map(s => {
const on = imageStyles.includes(s.v);
return (
<button
key={s.v} type="button" onClick={() => setImageStyle(s.v)}
key={s.v} type="button"
onClick={() => setImageStyles(on ? imageStyles.filter(x => x !== s.v) : [...imageStyles, s.v])}
className={`p-3 rounded-lg border text-left transition-colors ${
imageStyle === s.v ? 'border-accent bg-accent/10' : 'border-border bg-surface2 hover:border-gray-600'
on ? 'border-accent bg-accent/10' : 'border-border bg-surface2 hover:border-gray-600'
}`}
>
<div className="text-sm font-medium">{s.label}</div>
<div className="text-xs text-gray-500 mt-0.5">{s.desc}</div>
</button>
))}
);
})}
</div>
</div>