forked from admin/zeropost-engine
feat: расширенная анкета канала + промпт-инжиниринг для человечности
БД (новые таблицы): - channel_style: тон/юмор/длина/структура/эмодзи/хэштеги/примеры постов/стоп-слова - channel_schedule: расписание, рубрики, источники, auto_publish - generation_jobs: добавлены user_id, tokens, cost, prompt_debug - posts: связка с job, image_url, scheduling Новый модуль services/promptBuilder.js: - HUMANITY_RULES: правила живого текста (антипаттерны, личный голос, конкретика) - buildPostSystemPrompt: собирает промпт из канала + few-shot примеров - buildCritiquePrompt: self-critique для очистки от AI-следов services/ai.js: - generatePost теперь использует 2-step chain: генерация + critique - temperature настроен (0.9 для разнообразия) - возвращает usage/токены services/channels.js: новый сервис, работа с тремя таблицами транзакционно routes/channels.js: CRUD под расширенную модель routes/generate.js: связка с channelId, передача в worker Результат на тестах: пост следует стилю few-shot примеров, без AI-маркеров
This commit is contained in:
+109
-42
@@ -10,57 +10,124 @@ pool.on('error', (err) => {
|
||||
const query = (text, params) => pool.query(text, params);
|
||||
|
||||
const migrate = async () => {
|
||||
// users — расширили под план/api_key/баланс
|
||||
await query(`
|
||||
CREATE TABLE IF NOT EXISTS generation_jobs (
|
||||
id SERIAL PRIMARY KEY,
|
||||
type VARCHAR(50) NOT NULL, -- 'article', 'post', 'caption'
|
||||
channel_id INTEGER,
|
||||
topic TEXT,
|
||||
prompt TEXT,
|
||||
result TEXT,
|
||||
status VARCHAR(20) DEFAULT 'pending', -- pending, processing, done, failed
|
||||
error TEXT,
|
||||
metadata JSONB DEFAULT '{}',
|
||||
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS channels (
|
||||
id SERIAL PRIMARY KEY,
|
||||
user_id INTEGER NOT NULL,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
tg_channel_id VARCHAR(255),
|
||||
bot_token TEXT,
|
||||
topic TEXT,
|
||||
tone VARCHAR(100) DEFAULT 'neutral',
|
||||
language VARCHAR(10) DEFAULT 'ru',
|
||||
post_schedule JSONB DEFAULT '{}',
|
||||
is_active BOOLEAN DEFAULT true,
|
||||
created_at TIMESTAMPTZ DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id SERIAL PRIMARY KEY,
|
||||
email VARCHAR(255) UNIQUE NOT NULL,
|
||||
password TEXT NOT NULL,
|
||||
plan VARCHAR(20) DEFAULT 'free', -- free, pro, enterprise
|
||||
name VARCHAR(255),
|
||||
plan VARCHAR(20) DEFAULT 'free',
|
||||
api_key VARCHAR(64) UNIQUE,
|
||||
created_at TIMESTAMPTZ DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS posts (
|
||||
id SERIAL PRIMARY KEY,
|
||||
channel_id INTEGER REFERENCES channels(id),
|
||||
job_id INTEGER REFERENCES generation_jobs(id),
|
||||
content TEXT NOT NULL,
|
||||
status VARCHAR(20) DEFAULT 'draft', -- draft, scheduled, published, failed
|
||||
scheduled_at TIMESTAMPTZ,
|
||||
published_at TIMESTAMPTZ,
|
||||
tg_message_id BIGINT,
|
||||
metadata JSONB DEFAULT '{}',
|
||||
tokens_used BIGINT DEFAULT 0,
|
||||
created_at TIMESTAMPTZ DEFAULT NOW()
|
||||
);
|
||||
`);
|
||||
|
||||
// channels — базовые настройки канала
|
||||
await query(`
|
||||
CREATE TABLE IF NOT EXISTS channels (
|
||||
id SERIAL PRIMARY KEY,
|
||||
user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
tg_channel_id VARCHAR(255),
|
||||
tg_username VARCHAR(255),
|
||||
bot_token TEXT,
|
||||
niche TEXT, -- узкая тематика
|
||||
audience TEXT, -- описание ЦА
|
||||
goal VARCHAR(50) DEFAULT 'educational', -- educational/news/entertainment/expert/sales
|
||||
language VARCHAR(10) DEFAULT 'ru',
|
||||
region VARCHAR(50) DEFAULT 'ru', -- ru/cis/west
|
||||
is_active BOOLEAN DEFAULT true,
|
||||
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ DEFAULT NOW()
|
||||
);
|
||||
`);
|
||||
|
||||
// channel_style — стилевые настройки (1:1 с channels)
|
||||
await query(`
|
||||
CREATE TABLE IF NOT EXISTS channel_style (
|
||||
channel_id INTEGER PRIMARY KEY REFERENCES channels(id) ON DELETE CASCADE,
|
||||
tone VARCHAR(50) DEFAULT 'friendly', -- friendly/serious/ironic/provocative/academic/custom
|
||||
tone_custom TEXT, -- если tone='custom'
|
||||
formality VARCHAR(20) DEFAULT 'informal', -- formal(вы)/informal(ты)
|
||||
humor VARCHAR(20) DEFAULT 'moderate', -- none/dry/moderate/playful
|
||||
post_length VARCHAR(20) DEFAULT 'medium', -- short(<300)/medium(300-800)/long(800-2000)
|
||||
structure VARCHAR(50) DEFAULT 'mixed', -- plain/lists/headers/mixed
|
||||
emoji_level VARCHAR(20) DEFAULT 'moderate', -- none/moderate/active
|
||||
hashtags_mode VARCHAR(20) DEFAULT 'end', -- none/end/inline
|
||||
cta_mode VARCHAR(20) DEFAULT 'sometimes', -- always/sometimes/never
|
||||
example_posts JSONB DEFAULT '[]'::jsonb, -- массив строк-эталонов (few-shot)
|
||||
banned_words JSONB DEFAULT '[]'::jsonb, -- стоп-слова
|
||||
banned_topics JSONB DEFAULT '[]'::jsonb, -- запрещённые темы
|
||||
expertise JSONB DEFAULT '[]'::jsonb, -- темы где автор силён
|
||||
updated_at TIMESTAMPTZ DEFAULT NOW()
|
||||
);
|
||||
`);
|
||||
|
||||
// channel_schedule — расписание и рубрики
|
||||
await query(`
|
||||
CREATE TABLE IF NOT EXISTS channel_schedule (
|
||||
channel_id INTEGER PRIMARY KEY REFERENCES channels(id) ON DELETE CASCADE,
|
||||
posts_per_day INTEGER DEFAULT 1,
|
||||
time_slots JSONB DEFAULT '[]'::jsonb, -- ["09:00","18:00"]
|
||||
timezone VARCHAR(50) DEFAULT 'Europe/Moscow',
|
||||
rubrics JSONB DEFAULT '[]'::jsonb, -- [{name,description,days:[1,3,5]}]
|
||||
sources JSONB DEFAULT '[]'::jsonb, -- [{type:'rss',url:'...'},{type:'tg',channel:'@...'}]
|
||||
auto_publish BOOLEAN DEFAULT false, -- авто-постинг без подтверждения
|
||||
updated_at TIMESTAMPTZ DEFAULT NOW()
|
||||
);
|
||||
`);
|
||||
|
||||
// generation_jobs — задачи генерации
|
||||
await query(`
|
||||
CREATE TABLE IF NOT EXISTS generation_jobs (
|
||||
id SERIAL PRIMARY KEY,
|
||||
user_id INTEGER REFERENCES users(id) ON DELETE SET NULL,
|
||||
channel_id INTEGER REFERENCES channels(id) ON DELETE SET NULL,
|
||||
type VARCHAR(50) NOT NULL, -- post/article/topics/rewrite
|
||||
topic TEXT,
|
||||
rubric VARCHAR(255),
|
||||
prompt_debug TEXT, -- финальный промпт для отладки
|
||||
result TEXT,
|
||||
tokens_in INTEGER,
|
||||
tokens_out INTEGER,
|
||||
cost_cents INTEGER DEFAULT 0,
|
||||
status VARCHAR(20) DEFAULT 'pending',
|
||||
error TEXT,
|
||||
metadata JSONB DEFAULT '{}'::jsonb,
|
||||
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ DEFAULT NOW()
|
||||
);
|
||||
`);
|
||||
|
||||
// posts — готовые посты (черновики и опубликованные)
|
||||
await query(`
|
||||
CREATE TABLE IF NOT EXISTS posts (
|
||||
id SERIAL PRIMARY KEY,
|
||||
channel_id INTEGER REFERENCES channels(id) ON DELETE CASCADE,
|
||||
job_id INTEGER REFERENCES generation_jobs(id) ON DELETE SET NULL,
|
||||
content TEXT NOT NULL,
|
||||
image_url TEXT,
|
||||
status VARCHAR(20) DEFAULT 'draft', -- draft/scheduled/published/failed
|
||||
scheduled_at TIMESTAMPTZ,
|
||||
published_at TIMESTAMPTZ,
|
||||
tg_message_id BIGINT,
|
||||
metadata JSONB DEFAULT '{}'::jsonb,
|
||||
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ DEFAULT NOW()
|
||||
);
|
||||
`);
|
||||
|
||||
// индексы
|
||||
await query(`
|
||||
CREATE INDEX IF NOT EXISTS idx_channels_user ON channels(user_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_posts_channel ON posts(channel_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_posts_scheduled ON posts(scheduled_at) WHERE status='scheduled';
|
||||
CREATE INDEX IF NOT EXISTS idx_jobs_status ON generation_jobs(status);
|
||||
CREATE INDEX IF NOT EXISTS idx_jobs_user ON generation_jobs(user_id);
|
||||
`);
|
||||
|
||||
console.log('[DB] Migrations applied');
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user