const { Pool } = require('pg') // XFlow DB (through SSH tunnel) const DB_URL = 'postgresql://xflow:xflow_pass@localhost:5433/xflow' const pool = new Pool({ connectionString: DB_URL }) const CANVAS_ID = 'npb-aipb-project-brief' const CANVAS_NAME = '🎨 NPB/AIPB — Project Brief' const OWNER_ID = 'GqbpMyUW0NaxjIy5nvCiXCiQu9t2' // ───────────────────────────────────────────── // NODES — merged from NPB.json + XFlow pre-prompt context // ───────────────────────────────────────────── const nodes = [ // ── CENTRAL NODE ────────────────────────── { id: 'main', type: 'text', x: -474, y: -603, width: 450, height: 220, label: '🎨 AIPB — AI Portrait Builder', text: `# 🎨 AIPB\n\n**Генератор художественных портретов с ИИ.**\n\n- **Статус:** v1.1.0 Architecture\n- **Стек:** Nuxt 3, MediaPipe, Replicate, Firebase\n- **Концепт:** Парные экраны (Display + Remote)\n- **Фишка:** Управление жестами или с планшета`, state: 'pending', style: { background: '#1e293b', border: '#334155' } }, // ── TECH STACK ──────────────────────────── { id: 'tech-stack', type: 'text', x: -1049, y: -262, width: 400, height: 380, label: '🏗️ Tech Stack', text: `# 🏗️ Стек AIPB\n\n- **Framework:** Nuxt 3 (SSR + Nitro)\n- **CV:** MediaPipe Hands (JS)\n- **AI APIs:** Replicate (SDXL), Face Swap\n- **Backend/Sync:** Firebase RTDB\n- **Auth:** Firebase Auth\n- **State:** Pinia + Firebase Listeners\n- **UI:** TailwindCSS + Animations`, state: 'pending', style: { background: '#1e293b', border: '#334155' } }, // ── AUTH SYSTEM ─────────────────────────── { id: 'auth-system', type: 'text', x: -1049, y: 180, width: 400, height: 300, label: '🔐 Auth Stack', text: `# 🔐 Firebase Auth & Roles\n\n- **Admin:** Управление киосками, стата\n- **Kiosk (Service):** Авторизация самой точки\n- **Remote (Anonymous):** Временный токен для планшета\n- **Security:** Firebase Rules (доступ только к своей сессии)`, state: 'pending', style: { background: '#1e293b', border: '#334155' } }, // ── DUAL DEVICE ─────────────────────────── { id: 'dual-device', type: 'text', x: 100, y: -603, width: 450, height: 320, label: '📡 Синхронизация пар', text: `# 📡 Dual-Device Sync\n\n- **Pairing:** Генерация PIN/QR кода на ПК\n- **Connection:** Сканирование с планшета\n- **Realtime:** Firebase RTDB для синхронизации State\n- **Latency:** <100ms отклик интерфейса`, state: 'pending', style: { background: '#0f766e', border: '#115e59' } }, // ── KIOSK ARCHITECTURE ──────────────────── { id: 'architecture', type: 'text', x: 600, y: -603, width: 450, height: 350, label: '🖥️ Kiosk Architecture', text: `# 🖥️ Разделение UI\n\n**1. Display View (ТВ/ПК):**\n- Трансляция камеры\n- Красивые idle-анимации\n- Рендер ИИ и таймеров\n- QR-код для скачивания\n\n**2. Controller View (Планшет):**\n- Кнопки выбора (Пол, Стиль, Язык)\n- Кнопка спуска затвора\n- Ввод Email/Telegram`, state: 'pending', style: { background: '#0f766e', border: '#115e59' } }, // ── USER FLOW ───────────────────────────── { id: 'user-flow', type: 'text', x: -128, y: 147, width: 450, height: 400, label: '🔄 User Flow (8 Steps)', text: `# 🔄 Процесс генерации\n\n1. **Welcome** — Старт на планшете/жестом\n2. **Language** — Выбор языка\n3. **Gender** — Выбор пола (M/F)\n4. **Style** — Выбор стиля\n5. **Capture** — Фото с таймером (3с)\n6. **Confirm** — Проверка кадра на планшете\n7. **Loading** — Процесс ИИ генерации (на экране)\n8. **Result** — Сохранение и шеринг`, state: 'pending', style: { background: '#1e293b', border: '#334155' } }, // ── GESTURE CONTROLS ────────────────────── { id: 'gestures', type: 'text', x: -566, y: -72, width: 400, height: 300, label: '👋 Gesture Controls', text: `# 👋 Управление жестами\n\n- **✊ Fist:** Клик / Выбор\n- **✌️ V-sign:** Скролл вниз\n- **🖐️ Palm:** Пауза / Стоп\n\n*Альтернатива или дополнение к планшету*\n*Используется MediaPipe Hands (WASM в браузере)*`, state: 'pending', style: { background: '#1e293b', border: '#334155' } }, // ── ROADMAP ─────────────────────────────── { id: 'roadmap', type: 'text', x: 380, y: 147, width: 400, height: 300, label: '🚀 Roadmap', text: `# 🚀 Планы (v1.2.0+)\n\n- [ ] Прямая интеграция с TG-ботом\n- [ ] Админ-панель со статистикой\n- [ ] Оффлайн фоллбэк для обрыва связи\n- [ ] Платные премиум-стили\n- [ ] Галерея работ сообщества`, state: 'pending', style: { background: '#1e293b', border: '#334155' } }, // ── AI STYLES ───────────────────────────── { id: 'ai-styles', type: 'text', x: 850, y: 147, width: 400, height: 320, label: '🎭 AI Styles', text: `# 🎭 Художественные стили\n\n- **Cinematic:** Кино-освещение\n- **Anime:** Стилизация под аниме\n- **Fantasy:** Магическая атмосфера\n- **Cyberpunk:** Неон и технологии\n- **Professional:** Студийное фото\n- **Artistic:** Масляная живопись\n\n*Провайдер: Replicate (SDXL)*`, state: 'pending', style: { background: '#312e81', border: '#3730a3' } }, // ── INFRASTRUCTURE (SERVER) ─────────────── { id: 'infrastructure', type: 'text', x: 850, y: -603, width: 450, height: 400, label: '🖥️ Инфраструктура сервера', text: `# 🖥️ Сервер\n\n- **VM (Debian):** 10.0.0.120\n- **Jump-host:** alex@178.238.78.98\n- **Docker Network:** \`infra\`\n- **Services:**\n - \`postgres:5432\` (Shared PostgreSQL)\n - \`gitea:3001\` (Git server)\n - \`xflow:3002\` (XFlow CRM)\n - \`npb-app:3000\` (NPB/AIPB — наш проект)\n- **Reverse Proxy:** Nginx (FastPanel)\n\n**SSH-туннель к БД:**\n\`\`\`\nssh -J alex@178.238.78.98 -L 5432:localhost:5432 root@10.0.0.120\n\`\`\``, state: 'pending', style: { background: '#1c1917', border: '#44403c' } }, // ── DEPLOYMENT ──────────────────────────── { id: 'deployment', type: 'text', x: -474, y: 650, width: 450, height: 300, label: '🚀 Деплой & CI/CD', text: `# 🔄 Деплой (Gitea Actions)\n\n1. \`git push\` → Gitea\n2. Gitea Actions запускает \`deploy.yml\`\n3. Runner делает \`git pull\` на сервере\n4. Пересобирает Docker-контейнер \`npb-app\`\n\n**Время деплоя:** ~3-5 минут\n\n⚠️ В PowerShell — команды по одной, без \`&&\``, state: 'pending', style: { background: '#14532d', border: '#166534' } }, // ── KNOWN ISSUES ────────────────────────── { id: 'known-issues', type: 'text', x: -1049, y: 580, width: 400, height: 300, label: '🐛 Известные проблемы', text: `# 🐛 Известные проблемы\n\n- **PWA:** Clear site data при non-precached-url ошибке\n- **Gitea push 403:** Токен мог истечь — проверить \`GITEA_TOKEN\` в .env\n- **AI Key:** Groq ключ может быть невалидным — обновить в .env\n- **Logs:** Убрать debug-логи из \`execute.post.ts\` перед продом`, state: 'pending', style: { background: '#450a0a', border: '#7f1d1d' } }, // ── DEV TIPS ────────────────────────────── { id: 'dev-tips', type: 'text', x: 380, y: 580, width: 450, height: 300, label: '💡 Dev Tips', text: `# 💡 Советы по разработке\n\n- **Локальный запуск:** \`npm run dev\`\n- **SSH-туннель к БД:**\n \`ssh -J alex@178.238.78.98 -L 5432:localhost:5432 root@10.0.0.120\`\n- **Список пользователей:** \`node scripts/list_users.cjs\`\n- **Тест AI:** \`node scripts/test_qwen.cjs\`\n- **Не использовать \`&&\` в PowerShell!**`, state: 'pending', style: { background: '#1e293b', border: '#334155' } } ] // ───────────────────────────────────────────── // EDGES — connections between nodes // ───────────────────────────────────────────── const edges = [ // Main → top cluster { id: 'e1', source: 'main', target: 'dual-device' }, { id: 'e2', source: 'dual-device', target: 'architecture' }, { id: 'e3', source: 'architecture', target: 'infrastructure' }, // Main → left cluster { id: 'e4', source: 'main', target: 'tech-stack' }, { id: 'e5', source: 'tech-stack', target: 'auth-system' }, // Main → center/bottom cluster { id: 'e6', source: 'main', target: 'gestures' }, { id: 'e7', source: 'main', target: 'user-flow' }, { id: 'e8', source: 'user-flow', target: 'roadmap' }, { id: 'e9', source: 'roadmap', target: 'ai-styles' }, // Bottom { id: 'e10', source: 'main', target: 'deployment' }, { id: 'e11', source: 'auth-system', target: 'known-issues' }, { id: 'e12', source: 'deployment', target: 'dev-tips' } ] // ───────────────────────────────────────────── // DB UPSERT // ───────────────────────────────────────────── async function run() { try { const check = await pool.query('SELECT id FROM "Canvas" WHERE id = $1', [CANVAS_ID]) if (check.rows.length > 0) { await pool.query( 'UPDATE "Canvas" SET nodes = $1, edges = $2, name = $3, "updatedAt" = NOW() WHERE id = $4', [JSON.stringify(nodes), JSON.stringify(edges), CANVAS_NAME, CANVAS_ID] ) console.log('✅ NPB/AIPB Brief canvas UPDATED!') } else { await pool.query( 'INSERT INTO "Canvas" (id, name, "ownerId", "isPublic", nodes, edges, "createdAt", "updatedAt") VALUES ($1, $2, $3, true, $4, $5, NOW(), NOW())', [CANVAS_ID, CANVAS_NAME, OWNER_ID, JSON.stringify(nodes), JSON.stringify(edges)] ) console.log('✅ NPB/AIPB Brief canvas CREATED!') } console.log(`\n📊 Stats: ${nodes.length} nodes, ${edges.length} edges`) console.log(`🔗 Canvas ID: ${CANVAS_ID}`) } catch (e) { console.error('❌ Error:', e.message) } finally { await pool.end() } } run()