Перейти к содержанию

Geni_AI_v015-3 (HfvL0uSdAtwjBN7u)

Raw workflow JSON

{
  "updatedAt": "2025-12-10T02:57:21.000Z",
  "createdAt": "2025-12-09T17:22:27.435Z",
  "id": "HfvL0uSdAtwjBN7u",
  "name": "Geni_AI_v015-3",
  "description": null,
  "active": true,
  "isArchived": false,
  "nodes": [
    {
      "parameters": {
        "rules": {
          "values": [
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict",
                  "version": 2
                },
                "conditions": [
                  {
                    "id": "78542b6d-15c2-4809-935c-49d1e17dcc1e",
                    "leftValue": "={{ $json.tool }}",
                    "rightValue": "account",
                    "operator": {
                      "type": "string",
                      "operation": "equals",
                      "name": "filter.operator.equals"
                    }
                  }
                ],
                "combinator": "and"
              }
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict",
                  "version": 2
                },
                "conditions": [
                  {
                    "id": "dfbae9da-3cc7-488e-9f71-f21a4b9b4345",
                    "leftValue": "={{ $json.tool }}",
                    "rightValue": "lang",
                    "operator": {
                      "type": "string",
                      "operation": "equals",
                      "name": "filter.operator.equals"
                    }
                  }
                ],
                "combinator": "and"
              }
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict",
                  "version": 2
                },
                "conditions": [
                  {
                    "id": "19714412-73ff-4c46-ba9e-4f4276157e72",
                    "leftValue": "={{ $json.tool }}",
                    "rightValue": "instruction",
                    "operator": {
                      "type": "string",
                      "operation": "equals",
                      "name": "filter.operator.equals"
                    }
                  }
                ],
                "combinator": "and"
              }
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict",
                  "version": 2
                },
                "conditions": [
                  {
                    "id": "d6fc6a81-7010-4119-9d91-c66d79e68ac8",
                    "leftValue": "={{ $json.tool }}",
                    "rightValue": "=flux",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    }
                  }
                ],
                "combinator": "and"
              }
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.switch",
      "typeVersion": 3.2,
      "position": [
        1328,
        2016
      ],
      "id": "f6103241-cbad-4932-a4e4-d845d2eb65c2",
      "name": "Switch1"
    },
    {
      "parameters": {
        "updates": [
          "message",
          "callback_query",
          "pre_checkout_query"
        ],
        "additionalFields": {}
      },
      "id": "04d5c88f-05c1-49e8-b34b-26ceb2ab22d2",
      "name": "Telegram Trigger1",
      "type": "n8n-nodes-base.telegramTrigger",
      "typeVersion": 1,
      "position": [
        -1568,
        1952
      ],
      "webhookId": "aaa5322f-da8f-4d75-b626-ae10bb379fbe",
      "credentials": {
        "telegramApi": {
          "id": "ur7jSUPdiAaPVhCf",
          "name": "Geni AI"
        }
      }
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://api.telegram.org/bot8495842221:AAHBriXvzudyazB2f8z3ilGQ8A5ESdjjtl8/sendMessage",
        "sendBody": true,
        "bodyParameters": {
          "parameters": [
            {
              "name": "chat_id",
              "value": "={{ $json.chatId }}"
            },
            {
              "name": "text",
              "value": "={{ $json.replyText }}"
            },
            {
              "name": "parse_mode",
              "value": "HTML"
            },
            {
              "name": "reply_markup",
              "value": "={{ $json.replyMarkup }}"
            },
            {
              "name": "disable_notification",
              "value": "={{ $json.silent }}"
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        624,
        1792
      ],
      "id": "69d935ba-a076-4654-bc52-a14458e6180e",
      "name": "reply_markup",
      "retryOnFail": true
    },
    {
      "parameters": {
        "jsCode": "// === MENU (RU/EN localization) ===\n// Keep original logic; add Account section + \"flat\" actions inside Account\n\n// ---- state\nconst sd = $getWorkflowStaticData('global');\nsd.menu     = sd.menu     || {};\nsd.session  = sd.session  || {};\nsd.userLang = sd.userLang || {};\nsd.userSub  = sd.userSub  || {}; // { [chatId]: { active: boolean } } optional cache\n\n// ---- input\nconst msg    = $json.message || {};\nconst chatId = String(msg.chat?.id || $json.chat?.id || '');\nconst msgId  = msg.message_id;\nconst textIn = String(msg.text ?? msg.caption ?? '').trim();\n\n// ---- language resolve: 1) payload.lang 2) cached 3) Telegram UI hint 4) ru\nlet lang = (typeof $json.lang === 'string' && /^(ru|en)$/i.test($json.lang)) ? $json.lang.toLowerCase() : null;\nif (!lang) lang = sd.userLang[chatId] || null;\nif (!lang) lang = (/^en/i.test(String(msg.from?.language_code || ''))) ? 'en' : 'ru';\nsd.userLang[chatId] = lang;\n\n// ---- localization dict\nconst I18N = {\n  ru: {\n    home: '🏠 Главная',\n    back: '⬅️ Назад',\n    account: '👤 Аккаунт',\n    mainTitle: '🏠 Главная страница',\n    mainDesc: 'Выберите инструмент',\n    help: '❔ Помощь',\n    helpDesc: 'Раздел помощи',\n    helpInstruction: '📘 Инструкция',\n    helpInstructionDesc: 'Как пользоваться ботом',\n    language: '🌐 Язык',\n    languageDesc: 'Выбор языка интерфейса',\n    image: '🖼 Image',\n    imageDesc: 'Генерация изображений',\n    gpt: '💬 GPT',\n    gptDesc: 'Чат с LLM',\n    audio: '🎵 Audio',\n    audioDesc: 'Аудио-инструменты',\n    video: '🎬 Video',\n    videoDesc: 'Видео-инструменты',\n    seedream: '🌊 Seedream',\n    nanobanana: '🍌 Nano Banana',\n    flux: '⚡️ FLUX',\n    fluxDesc: 'Генерация изображения с FLUX',\n    seedance: '🎞 Seedance',\n    veo: '⭕ Veo',\n    sora: '🌙 Sora 2',\n    settings: '⚙️ Settings',\n    placeholder: 'Меню',\n\n    // Account\n    accTitle: '👤 Аккаунт',\n    accDesc: 'Управление аккаунтом',\n    accBalance: '💰 Баланс',\n    accStatus: '📦 Статус',\n    accBuySub: '💳 Купить подписку',\n    accBuyCreds: '⚡️ Купить кредиты',\n\n    // NEW: Community\n    community: '👥 Сообщество',\n    communityDesc: 'Ссылка на канал @voyakin_geni',\n  },\n  en: {\n    home: '🏠 Home',\n    back: '⬅️ Back',\n    account: '👤 Account',\n    mainTitle: '🏠 Home',\n    mainDesc: 'Pick a tool',\n    help: '❔ Help',\n    helpDesc: 'Help section',\n    helpInstruction: '📘 Instructions',\n    helpInstructionDesc: 'How to use the bot',\n    language: '🌐 Language',\n    languageDesc: 'Choose interface language',\n    image: '🖼 Image',\n    imageDesc: 'Image generation',\n    gpt: '💬 GPT',\n    gptDesc: 'Chat with LLM',\n    audio: '🎵 Audio',\n    audioDesc: 'Audio tools',\n    video: '🎬 Video',\n    videoDesc: 'Video tools',\n    seedream: '🌊 Seedream',\n    nanobanana: '🍌 Nano Banana',\n    flux: '⚡️ FLUX',\n    fluxDesc: 'Generating an image with FLUX',\n    seedance: '🎞 Seedance',\n    veo: '⭕ Veo',\n    sora: '🌙 Sora 2',\n    settings: '⚙️ Settings',\n    placeholder: 'Menu',\n\n    // Account\n    accTitle: '👤 Account',\n    accDesc: 'Manage your account',\n    accBalance: '💰 Balance',\n    accStatus: '📦 Status',\n    accBuySub: '💳 Buy subscription',\n    accBuyCreds: '⚡️ Buy credits',\n\n    // NEW: Community\n    community: '👥 Community',\n    communityDesc: 'Link to channel @voyakin_geni',\n  }\n};\nconst L = (k)=> (I18N[lang]||I18N.ru)[k] ?? k;\n\n// ---- webapps\nconst WEBAPP_ACCOUNT  = 'https://tgmenu.pages.dev/account';\nconst WEBAPP_SETTINGS = 'https://app-ru.geni-ai.online/settings';\n\n// ---- menu tree (localized)\nconst MENU = {\n  key: '', title: L('mainTitle'), desc: L('mainDesc'), children: [\n    { key: 'image',  title: L('image'),  desc: L('imageDesc'), children: [\n      // [V1 HIDE] Seedream / Nano Banana временно отключены\n      { key: 'seedream',    title: L('seedream'), desc: '—', command: 'seedream', children: [\n         { key: 'settings', title: L('settings'), type: 'webapp', webUrl: WEBAPP_SETTINGS + '/seedream.html?tool=seedream' },\n       ]},\n      // { key: 'nano banana', title: L('nanobanana'), desc: '—', command: 'nano banana', children: [\n      //   { key: 'settings', title: L('settings'), type: 'webapp', webUrl: WEBAPP_SETTINGS + '/nanobanana.html' },\n      // ]},\n      { key: 'flux',        title: L('flux'), desc: L('fluxDesc'), command: 'flux', children: [\n        { key: 'settings', title: L('settings'), type: 'webapp', webUrl: WEBAPP_SETTINGS + '/flux3.html?tool=flux' },\n      ]},\n    ]},\n\n    // [V1 HIDE] GPT / Audio / Video временно отключены\n    // { key: 'gpt',   title: L('gpt'),   desc: L('gptDesc'),   command: 'gpt' },\n    // { key: 'audio', title: L('audio'), desc: L('audioDesc'), command: 'audio' },\n    // { key: 'video', title: L('video'), desc: L('videoDesc'), command: 'video', children: [\n    //   { key: 'seedance', title: L('seedance'), desc: '—', command: 'seedance', children: [\n    //     { key: 'settings', title: L('settings'), type: 'webapp', webUrl: WEBAPP_SETTINGS + '/seedance.html' }\n    //   ]},\n    //   { key: 'veo', title: L('veo'), desc: '—', command: 'veo', children: [\n    //     { key: 'settings', title: L('settings'), type: 'webapp', webUrl: WEBAPP_SETTINGS + '/veo.html' }\n    //   ]},\n    //   { key: 'sora', title: L('sora'), desc: '—', command: 'sora', children: [\n    //     { key: 'settings', title: L('settings'), type: 'webapp', webUrl: WEBAPP_SETTINGS + '/sora_2.html' }\n    //   ]},\n    // ]},\n\n    { key: 'help',  title: L('help'),  desc: L('helpDesc'),  children: [\n      { key: 'language', title: L('language'), desc: L('languageDesc'), command: 'lang' },\n      { key: 'instruction', title: L('helpInstruction'), desc: L('helpInstructionDesc'), command: 'instruction' },\n    ]},\n\n    // ACCOUNT (top-level)\n    { key: 'account', title: L('accTitle'), desc: L('accDesc'), command: 'account', children: [\n      { key: 'balance',     title: L('accBalance'),  desc: '—' },\n     // { key: 'status',      title: L('accStatus'),   desc: '—' },\n      { key: 'buy_sub',     title: L('accBuySub'),   desc: '—' },   // UI-сильно скрыт ниже\n      { key: 'buy_credits', title: L('accBuyCreds'), desc: '—' },\n    ]},\n\n     { key: 'community', title: L('community'), desc: L('communityDesc'), command: 'community' },\n  ]\n};\n\n// ---- helpers\nconst BTN_MAIN = L('home');\nconst BTN_BACK = L('back');\nconst BTN_ACCOUNT = L('account'); // web_app button text at bottom (сейчас не используем)\n\nconst hasMedia =\n  (Array.isArray(msg.photo) && msg.photo.length>0) ||\n  !!msg.document;\n\nconst norm = s => (s||'').normalize('NFKC').toLowerCase()\n  .replace(/[\\p{Emoji_Presentation}\\p{Emoji}\\uFE0F]/gu,'')\n  .replace(/[\\u0300-\\u036f]/g,'')\n  .replace(/[^\\p{L}\\p{N}]+/gu,'').trim();\n\nconst split = p => p ? p.split('/').filter(Boolean) : [];\nfunction nodeByPath(path){\n  if(!path) return MENU;\n  let n=MENU;\n  for(const part of split(path)){\n    n=(n.children||[]).find(c=>c.key===part);\n    if(!n) return MENU;\n  }\n  return n;\n}\nfunction parentPath(path){ const a=split(path); a.pop(); return a.join('/'); }\nfunction childKeyByInput(path,txt){\n  const want=norm(txt); const n=nodeByPath(path);\n  const here=(n.children||[]).find(c=>[c.key,c.title].some(v=>norm(v)===want))?.key;\n  if (here) return here;\n  const root=(MENU.children||[]).find(c=>[c.key,c.title].some(v=>norm(v)===want))?.key;\n  return root||null;\n}\nfunction matchInAccount(txt){\n  const acc = (MENU.children||[]).find(c=>c.key==='account');\n  if (!acc) return null;\n  const want = norm(txt);\n  const hit = (acc.children||[]).find(c => [c.key,c.title].some(v => norm(v)===want));\n  return hit?.key || null; // 'balance'|'status'|'buy_sub'|'buy_credits'\n}\n\n// ---- nav/mode\nlet path = sd.menu[chatId]?.path || '';\nlet session = sd.session[chatId] || { tool:null };\nlet account_action = null; // <- FLAT actions inside account\n\nconst low = norm(textIn);\n\n// отдельный флаг именно для /start\nconst isStartCmd = /^\\/start\\b/i.test(textIn);\n\n// \"меню\" = /start, /menu и текстовые синонимы\nconst isMenuCmd =\n  isStartCmd ||\n  /^\\/menu\\b/i.test(textIn) ||\n  ['menu','main','home','меню','главная','домой'].includes(low);\n\nconst isBack    = [norm(BTN_BACK),'back','назад'].includes(low);\n\n// команды/шорткаты\nconst isBalanceCmd    = /^\\/balance\\b/i.test(textIn)     || low === 'balance'    || low === norm(L('accBalance'));\nconst isBuyCreditsCmd = /^\\/buy_credits\\b/i.test(textIn) || low === 'buycredits' || low === 'buy_credits';\nconst isLangCmd       = /^\\/lang\\b/i.test(textIn)        || low === 'lang';\n\n// navigation changes\nif (isMenuCmd) {\n  path=''; session.tool=null;\n} else if (isBack) {\n  const prev=parentPath(path);\n  if (split(path).length===1) session.tool=null;\n  path=prev;\n} else if (textIn) {\n  // direct \"Language\" jump: кнопка «Язык» ИЛИ /lang / lang\n  if (norm(textIn) === norm(L('language')) || isLangCmd) {\n    path='help/language';\n    session.tool='lang';\n  } else if (isBalanceCmd) {\n    // прямой вызов баланса: /balance или \"Баланс\"\n    path = 'account';\n    account_action = 'balance';\n    session.tool = 'account';\n  } else if (isBuyCreditsCmd) {\n    // прямой вызов покупки кредитов: /buy_credits\n    path = 'account';\n    account_action = 'buy_credits';\n    session.tool = 'account';\n  } else {\n    const k = childKeyByInput(path, textIn); // can be child of current or root\n    if (k){\n      const isRootChild = (MENU.children||[]).some(c=>c.key===k);\n\n      if (isRootChild) {\n        // go to root child as usual\n        path = k;\n        const chosen = nodeByPath(path);\n        if (chosen?.command) session.tool = chosen.command;\n        else session.tool = null;\n      } else {\n        // child of current node\n        if (path === 'account') {\n          // FLAT actions: keep path at 'account' and emit action\n          account_action = k;       // 'balance'|'status'|'buy_sub'|'buy_credits'\n          session.tool    = 'account';\n          // DO NOT change path\n        } else {\n          // normal deep navigation for other sections\n          path = path ? `${path}/${k}` : k;\n          const chosen = nodeByPath(path);\n          if (chosen?.command) session.tool = chosen.command;\n          else if (!chosen?.children?.length) session.tool = null;\n        }\n      }\n    }\n  }\n}\n\n// persist\nsd.menu[chatId]    = { path, updatedAt: new Date().toISOString(), lang };\nsd.session[chatId] = session;\n\nconst node = nodeByPath(path);\nconst cameFromNav = isMenuCmd || isBack || !!childKeyByInput(parentPath(path), textIn);\nconst isLangTool  = session.tool === 'lang';\nconst isAccountAction = session.tool === 'account' && !!account_action;\nconst isInstructionTool = session.tool === 'instruction';\n\n// shouldSendMenu: no menu when lang inline or account action (we'll send specific message)\nconst shouldSendMenu = (isLangTool || isAccountAction || isInstructionTool) ? false : cameFromNav;\nconst isPrompt = !!session.tool && !shouldSendMenu && (textIn || hasMedia);\n\n// ====== Home inline buttons (only on root + когда реально рисуем меню) ======\nlet homeInlineShow   = false;\nlet homeInlineText   = null;\nlet homeInlineMarkup = null;\n\nif (!path && shouldSendMenu) {\n  homeInlineShow = true;\n\n  // Текст под инлайн-кнопками\n  homeInlineText = (lang === 'ru')\n    ? '🚀 Быстрый старт'\n    : '🚀 Quick start';\n\n  // Пример кнопок — под себя подправишь\n  if (lang === 'ru') {\n    homeInlineMarkup = {\n      inline_keyboard: [\n        [\n          { text: '⚡️ FLUX',            callback_data: 'home:flux' },\n        ],\n        [\n          { text: '📘 Инструкция',      callback_data: 'home:instruction' },\n        ],\n        [\n          { text: '💰 Баланс',          callback_data: 'account:balance' },\n          { text: '⚡️ Купить кредиты',  callback_data: 'account:buy_credits' },\n        ],\n      ],\n    };\n  } else {\n    homeInlineMarkup = {\n      inline_keyboard: [\n        [\n          { text: '⚡️ FLUX',           callback_data: 'home:flux' },\n        ],\n        [\n          { text: '📘 Instructions',   callback_data: 'home:instruction' },\n        ],\n        [\n          { text: '💰 Balance',        callback_data: 'account:balance' },\n          { text: '⚡️ Buy credits',    callback_data: 'account:buy_credits' },\n        ],\n      ],\n    };\n  }\n}\n\n// ====== Keyboard (v1: hide subscription, keep credits) ======\nlet dynNode = node;\n\n// Мягко скрываем пункт \"buy_sub\" в аккаунте (UI), логика ветки buy_sub остаётся\nif (node.key === 'account') {\n  dynNode = {\n    ...node,\n    children: (node.children || []).filter(c => c.key !== 'buy_sub')\n  };\n}\n\n// Для community показываем клавиатуру как на главном экране (root MENU),\n// но текст/placeholder остаются от самого раздела community.\nconst keyboardNode = (node.key === 'community') ? MENU : dynNode;\n\n// reply keyboard (skip when lang inline or account action)\nlet replyMarkup = undefined, replyText = undefined;\nif (!isLangTool && !isAccountAction) {\n  const rows=[];\n  const webapps=(keyboardNode.children||[]).filter(c=>c.type==='webapp');\n  for (const c of webapps) {\n    rows.push([{ text: c.title, web_app: { url: c.webUrl } }]);\n  }\n\n  const normals=(keyboardNode.children||[]).filter(c=>c.type!=='webapp');\n  for (let i=0; i<normals.length; i+=2) {\n    rows.push(normals.slice(i,i+2).map(c => ({ text: c.title })));\n  }\n\n  const nav = [];\n  // На экране community кнопку Back не показываем,\n  // чтобы клавиатура была как у home (только Home).\n  const showBack = path && node.key !== 'community';\n  if (showBack) {\n    nav.push({ text: BTN_BACK });\n  }\n  nav.push({ text: BTN_MAIN });\n  rows.push(nav);\n\n\n  replyMarkup = {\n    keyboard: rows,\n    resize_keyboard: true,\n    one_time_keyboard: false,\n    selective: false,\n    input_field_placeholder: dynNode.title || L('placeholder'),\n  };\n  replyText = (path ? `${dynNode.title}\\n${dynNode.desc||''}` : L('mainTitle')).trim();\n}\n\n\n\n// prompt fields\nconst promptText = msg.caption ?? msg.text ?? '';\n\n// media\nlet photoFileId=null, file_kind=null, file_name=null, mime_type=null;\nconst doc = msg.document;\nif (doc?.file_id) {\n  const mt = String(doc.mime_type || '').toLowerCase();\n  const name = String(doc.file_name || '').toLowerCase();\n  const looksImage = mt.startsWith('image/') || /\\.(png|jpe?g|webp|bmp|gif|tiff?)$/.test(name);\n  if (looksImage) { photoFileId=doc.file_id; file_kind='document'; file_name=doc.file_name||null; mime_type=doc.mime_type||null; }\n}\nif (!photoFileId && Array.isArray(msg.photo) && msg.photo.length>0) {\n  const best = msg.photo[msg.photo.length-1];\n  photoFileId = best.file_id; file_kind='photo';\n}\n\n// output\nreturn [{\n  chatId,\n  tool: session.tool,\n  account_action: account_action || undefined,\n  intent: isLangTool ? 'LANG_PICK' : (isAccountAction ? 'ACCOUNT_ACTION' : (isInstructionTool ? 'INSTRUCTION' : undefined)),\n\n  shouldSendMenu,\n  isPrompt,\n  replyText,\n  replyMarkup,\n  silent: true,\n  prompt: promptText,\n\n  photo_file_id: photoFileId,\n  file_kind, file_name, mime_type,\n\n  deleteChatId: chatId,\n  deleteMessageId: msgId,\n  path,\n  lang,\n  is_start: isStartCmd,\n\n  // NEW:\n  homeInlineShow,\n  homeInlineText,\n  homeInlineMarkup,\n}];\n"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -96,
        2016
      ],
      "id": "a2fa5e5b-86d9-46e8-b5e8-946bee127bcc",
      "name": "menu"
    },
    {
      "parameters": {
        "jsCode": "// build_sub_invoice — подготовка инвойса Stars (XTR) для подписки Standard\n// ВХОД: ожидаем, что сверху пришло tool:'account' и account_action:'buy_sub' + chatId/msg\n// ВЫХОД: invoice (плоские поля для createInvoiceLink), robokassa_url, тексты UI\n\nconst sd = $getWorkflowStaticData('global');\n\n// --- Вход / контекст\nconst chatId = String($json.chatId || $json.message?.chat?.id || '');\nconst msgId  = $json.deleteMessageId || $json.message?.message_id || undefined;\n\nconst tool   = $json.tool || null;\nconst action = $json.account_action || null;\n\n// Язык: по входу → кэш → подсказка Telegram → ru\nlet lang = (typeof $json.lang === 'string' && /^(ru|en)$/i.test($json.lang)) \n  ? $json.lang.toLowerCase()\n  : (sd.userLang?.[chatId] || (String($json.message?.from?.language_code || '').startsWith('en') ? 'en' : 'ru'));\nsd.userLang = sd.userLang || {};\nsd.userLang[chatId] = lang;\n\n// Фильтр: запускаемся только на покупку подписки из меню аккаунта\nif (tool !== 'account' || action !== 'buy_sub') {\n  return [{ json: { skip: true, reason: 'not_account_buy_sub' } }];\n}\n\n// --- Конфигурация цены (звёзды = XTR, целое число)\nconst PLAN_KEY = 'standard';\nconst PRICE_STARS = Number(sd.pricing?.sub?.standard_stars) || 1; // TODO: поставь свою цену в звёздах\nconst DURATION_DAYS = 30;\n\n// --- Локализация\nconst T = (k) => {\n  const RU = {\n    title: 'Подписка Стандарт',\n    desc:  'Доступ ко всем инструментам на 30 дней',\n    label: `Стандарт (${DURATION_DAYS} дней)`,\n    prompt: `Выберите способ оплаты подписки «Стандарт» на ${DURATION_DAYS} дней.\\nСтоимость: ${PRICE_STARS} ⭐️`,\n    payStars: 'Оплатить звёздами',\n    payCard: 'Оплатить картой'\n  };\n  const EN = {\n    title: 'Standard Subscription',\n    desc:  'Access to all tools for 30 days',\n    label: `Standard (${DURATION_DAYS} days)`,\n    prompt: `Choose a payment method for “Standard” ${DURATION_DAYS}-day plan.\\nPrice: ${PRICE_STARS} ⭐️`,\n    payStars: 'Pay with Stars',\n    payCard: 'Pay by card'\n  };\n  const dict = (lang === 'en') ? EN : RU;\n  return dict[k] || k;\n};\n\n// --- Параметры для createInvoiceLink (Stars/XTR не требует provider_token)\nconst payload = `sub:${PLAN_KEY}:${chatId}:${Date.now()}`;\n\n// Важно: prices должен быть JSON-строкой массива LabeledPrice ({label, amount})\nconst prices = JSON.stringify([{ label: T('label'), amount: PRICE_STARS }]);\n\nconst invoice = {\n  title: T('title'),\n  description: T('desc'),\n  payload,\n  currency: 'XTR',\n  prices,\n  // Не указываем provider_token для Stars!\n  // Дополнительно можно добавить:\n  // photo_url: 'https://.../preview.jpg',\n  // need_name/address/... — НЕ нужно для Stars\n};\n\n// Плейсхолдер для оплаты картой (Robokassa) — просто ссылка на ваш бэкенд\nconst robokassa_url = `https://pay.example/robokassa?plan=${PLAN_KEY}&uid=${encodeURIComponent(chatId)}`;\n\n// Текст сообщения, которое отправим после получения invoice_link\nconst pay_prompt_text = T('prompt');\n\n// Возвращаем все данные для следующих нод\nreturn [{\n  json: {\n    chatId,\n    // Чтобы удалить исходное сообщение пользователя\n    deleteChatId: chatId,\n    deleteMessageId: msgId,\n\n    // Для следующего шага (HTTP Request -> createInvoiceLink)\n    invoice,          // плоские поля\n    // Для сообщения-кнопок\n    robokassa_url,\n    pay_prompt_text,\n    ui_texts: {\n      payStars: T('payStars'),\n      payCard: T('payCard')\n    },\n\n    meta: { tool, action, lang, plan: PLAN_KEY, price_stars: PRICE_STARS, duration_days: DURATION_DAYS }\n  }\n}];\n"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2032,
        432
      ],
      "id": "7bb0d7a4-a8ec-49f1-b251-7e67d34424bf",
      "name": "build_sub_invoice",
      "disabled": true
    },
    {
      "parameters": {
        "jsCode": "// Ловим и парсим pre_checkout_query (в т.ч. Stars: currency === \"XTR\")\n// Если апдейт не тот — ничего не отдаём.\n\nconst pq = $json.pre_checkout_query;\nif (!pq) {\n  return [];\n}\n\n// Базовые поля\nconst id           = String(pq.id || '');\nconst fromId       = Number(pq.from?.id || 0);\nconst username     = String(pq.from?.username || '');\nconst chatId       = String(pq.chat?.id || pq.from?.id || ''); // для приватных обычно = from.id\nconst currency     = String(pq.currency || '');  // XTR для звёзд\nconst total_amount = Number(pq.total_amount || 0);\nconst payload      = String(pq.invoice_payload || ''); // то, что вы закладывали при sendInvoice\n\n// Если нужно — валидация payload (план, срок, и т.д.)\n// Здесь просто прокидываем дальше.\nreturn [{\n  json: {\n    kind: 'pre_checkout',\n    chatId,\n    userId: fromId,\n    username,\n    pre_checkout_query_id: id,\n    currency,\n    total_amount,\n    payload,\n    // Решение об ок/не ок можно принимать здесь:\n    ok: true,\n    error_message: null\n  }\n}];\n"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -912,
        -1280
      ],
      "id": "d320d2ff-a421-415a-afa1-84f967c24d2e",
      "name": "payment.precheckout.parse"
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://api.telegram.org/bot8495842221:AAHBriXvzudyazB2f8z3ilGQ8A5ESdjjtl8/createInvoiceLink",
        "sendBody": true,
        "bodyParameters": {
          "parameters": [
            {
              "name": "title",
              "value": "={{ $json.invoice.title }}"
            },
            {
              "name": "description",
              "value": "={{ $json.invoice.description }}"
            },
            {
              "name": "payload",
              "value": "={{ $json.invoice.payload }}"
            },
            {
              "name": "currency",
              "value": "={{ $json.invoice.currency }}"
            },
            {
              "name": "prices",
              "value": "={{ $json.invoice.prices }}"
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        2336,
        432
      ],
      "id": "4279494d-0a2d-48eb-af5c-ed01d2b3f370",
      "name": "createInvoiceLink",
      "disabled": true
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://api.telegram.org/bot8495842221:AAHBriXvzudyazB2f8z3ilGQ8A5ESdjjtl8/answerPreCheckoutQuery",
        "sendBody": true,
        "bodyParameters": {
          "parameters": [
            {
              "name": "pre_checkout_query_id",
              "value": "={{ $json.pre_checkout_query_id }}"
            },
            {
              "name": "ok",
              "value": "={{ $json.ok }}"
            },
            {
              "name": "error_message",
              "value": "={{ $json.error_message }}"
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        -640,
        -1280
      ],
      "id": "b50f9952-0532-40f3-ad19-7c47386d768e",
      "name": "answerPreCheckoutQuery"
    },
    {
      "parameters": {
        "jsCode": "/**\n * Parse Telegram successful_payment → DB payload + ACK\n * Подключение:\n *  1) HTTP → Supabase (используй $json.db_event)\n *  2) Telegram → sendMessage (chat_id = $json.ack.chat_id, text = $json.ack.text)\n */\n\nconst sd = $getWorkflowStaticData('global');\nsd.userLang = sd.userLang || {};\nsd.payments = sd.payments || {}; // идемпотентность по charge_id\n\n// ---- Input\nconst upd = $json || {};\nconst msg = upd.message || {};\nconst sp  = msg.successful_payment || {};\n\nconst chatId  = String(msg.chat?.id || '');\nconst userId  = Number(msg.from?.id || chatId || 0);\nconst uname   = String(msg.from?.username || '');\nconst fname   = String([msg.from?.first_name, msg.from?.last_name].filter(Boolean).join(' ') || '');\nconst lang    = (sd.userLang[chatId] || (/^en/i.test(String(msg.from?.language_code||'')) ? 'en' : 'ru'));\n\nconst currency = String(sp.currency || 'XTR');\nconst amount   = Number(sp.total_amount || 0);\nconst payload_raw = String(sp.invoice_payload || '');\nconst tg_charge_id = String(sp.telegram_payment_charge_id || '');\nconst provider_charge_id = String(sp.provider_payment_charge_id || '');\nconst paid_at = new Date().toISOString();\n\n// ---- Safe date utils\nfunction addDays(baseIso, days){\n  const base = baseIso ? new Date(baseIso) : new Date();\n  if (!Number.isFinite(days)) return null;\n  if (isNaN(base.getTime())) return null;\n  base.setUTCDate(base.getUTCDate() + days);\n  try { return base.toISOString(); } catch { return null; }\n}\nfunction toHuman(iso, lang){\n  if (!iso) return '—';\n  const d = new Date(iso);\n  if (isNaN(d.getTime())) return '—';\n  try {\n    const loc = lang === 'en' ? 'en' : 'ru';\n    return d.toLocaleString(loc, {\n      year:'numeric', month:'2-digit', day:'2-digit',\n      hour:'2-digit', minute:'2-digit'\n    });\n  } catch {\n    const pad = n => String(n).padStart(2,'0');\n    return `${d.getUTCFullYear()}-${pad(d.getUTCMonth()+1)}-${pad(d.getUTCDate())} ${pad(d.getUTCHours())}:${pad(d.getUTCMinutes())} UTC`;\n  }\n}\n\n// ---- Parse payload (сохраняем nonce, но не используем его для срока)\nfunction parsePayload(raw){\n  const out = { raw, type:'unknown', plan:null, credits:null, user:null, extra:{} };\n  if (!raw) return out;\n  const parts = String(raw).split(':');\n  const head  = (parts[0]||'').toLowerCase();\n\n  if (['sub','subscription'].includes(head)) {\n    out.type = 'subscription';\n    out.plan = parts[1] || 'standard';\n    out.user = Number(parts[2] || userId) || null;\n    out.extra.nonce = parts[3] || null; // timestamp/nonce из payload, НЕ срок\n  } else if (['credits','cr'].includes(head)) {\n    out.type = 'credits';\n    const maybe = parts[1] || '';\n    const n = Number(maybe);\n    out.credits = Number.isFinite(n) && n>0 ? n : (parseInt(String(maybe).replace(/\\D+/g,''),10) || null);\n    out.plan = parts[1] || 'pack';\n  }\n  return out;\n}\nconst pp = parsePayload(payload_raw);\n\n// ---- Idempotency\nlet alreadyProcessed = false;\nif (tg_charge_id) {\n  if (sd.payments[tg_charge_id]) alreadyProcessed = true;\n  sd.payments[tg_charge_id] = true;\n}\n\n// ---- Business rules (жёстко: подписка = 30 дней и +270 кредитов)\nlet product_kind = pp.type;                 // 'subscription' | 'credits' | 'unknown'\nlet product_code = pp.plan || 'standard';\nlet term_days    = null;\nlet credits_add  = pp.credits;\n\nif (product_kind === 'subscription') {\n  term_days   = 30;       // фиксированный срок\n  credits_add = 270;      // фиксированное начисление\n}\n\n// Ожидаемая дата окончания\nconst expected_until_iso = (product_kind === 'subscription' && term_days)\n  ? addDays(paid_at, term_days)\n  : null;\n\n// ---- DB payload\nconst db_event = {\n  tg_user_id: userId,\n  chat_id: chatId,\n  username: uname,\n  full_name: fname,\n  currency,\n  amount,\n  payload_raw,\n  payload_type: product_kind,\n  plan: product_code,\n  term: null,\n  term_days: term_days,\n  credits: credits_add,\n  lang,\n  telegram_payment_charge_id: tg_charge_id,\n  provider_payment_charge_id: provider_charge_id,\n  paid_at,\n  expected_until: expected_until_iso\n};\n\n// ---- ACK text\nlet ackLines = [];\nif (lang === 'en') {\n  ackLines.push(alreadyProcessed ? 'ℹ️ Payment was already processed.' : '✅ Payment received.');\n  if (product_kind === 'subscription') {\n    ackLines.push(`Type: subscription (${product_code})`);\n    ackLines.push(`Term: ${term_days} days`);\n    ackLines.push(`Credits: +${credits_add}`);\n    if (expected_until_iso) ackLines.push(`Valid till (expected): ${toHuman(expected_until_iso,'en')}`);\n  } else if (product_kind === 'credits') {\n    ackLines.push(`Type: credits`);\n    ackLines.push(`Credits: +${credits_add ?? '—'}`);\n  } else {\n    ackLines.push(`Type: ${product_kind}`);\n  }\n  ackLines.push(`Amount: ${amount} ${currency}`);\n  if (tg_charge_id) ackLines.push(`Receipt: ${tg_charge_id}`);\n} else {\n  ackLines.push(alreadyProcessed ? 'ℹ️ Платёж уже был обработан.' : '✅ Оплата получена.');\n  if (product_kind === 'subscription') {\n    ackLines.push(`Тип: подписка (${product_code})`);\n    ackLines.push(`Срок: ${term_days} дн.`);\n    ackLines.push(`Кредиты: +${credits_add}`);\n    if (expected_until_iso) ackLines.push(`Действует до (ожид.): ${toHuman(expected_until_iso,'ru')}`);\n  } else if (product_kind === 'credits') {\n    ackLines.push(`Тип: кредиты`);\n    ackLines.push(`Кредиты: +${credits_add ?? '—'}`);\n  } else {\n    ackLines.push(`Тип: ${product_kind}`);\n  }\n  ackLines.push(`Сумма: ${amount} ${currency}`);\n  if (tg_charge_id) ackLines.push(`Чек: ${tg_charge_id}`);\n}\n\nconst ack = {\n  chat_id: chatId,\n  text: ackLines.join('\\n'),\n  disable_notification: true,\n};\n\n// ---- Return\nreturn [{\n  json: {\n    ok: true,\n    kind: 'payment_success',\n    chatId,\n    userId,\n    lang,\n    db_event,\n    ack\n  }\n}];\n"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -912,
        -1072
      ],
      "id": "f971aae9-adcd-460e-b37d-5e94e6a8e6f7",
      "name": "payment.parse_success"
    },
    {
      "parameters": {
        "method": "POST",
        "url": "=https://bwbsclwdkighhzlyiman.supabase.co/rest/v1/rpc/apply_telegram_payment",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "supabaseApi",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": " application/json"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={\n  \"p_tg_user_id\": {{ $json.userId }},\n  \"p_provider\": \"telegram-stars\",\n  \"p_charge_id\": \"{{ $json.db_event.provider_payment_charge_id }}\",\n  \"p_currency\": \"{{ $json.db_event.currency }}\",\n  \"p_amount\": {{ $json.db_event.amount }},\n  \"p_product_kind\": \"{{ $json.db_event.payload_type || 'unknown' }}\",\n  \"p_product_code\": \"{{ $json.db_event.plan || '' }}\",\n  \"p_credits_added\": {{ $json.db_event.credits || 0 }},\n  \"p_plan\": \"{{ $json.db_event.payload_type === 'subscription'\n        ? ($json.db_event.plan || 'standard')\n        : null }}\",\n  \"p_plan_days\": {{ $json.db_event.payload_type === 'subscription'\n        ? ($json.db_event.term_days || 0)\n        : 0 }},\n  \"p_lang\": \"{{ $json.lang || $json.db_event.lang || 'ru' }}\"\n}\n\n",
        "options": {}
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        -640,
        -1072
      ],
      "id": "e9bb008d-dcb9-4b6b-bc08-f67041f48bb1",
      "name": "apply_payment",
      "credentials": {
        "supabaseApi": {
          "id": "jGgpXKPYHiL193Rz",
          "name": "Supabase account"
        }
      }
    },
    {
      "parameters": {
        "rules": {
          "values": [
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict",
                  "version": 2
                },
                "conditions": [
                  {
                    "id": "58c08747-c6af-4557-9934-fd5bcba58813",
                    "leftValue": "={{ $json.pre_checkout_query }}",
                    "rightValue": "",
                    "operator": {
                      "type": "object",
                      "operation": "exists",
                      "singleValue": true
                    }
                  }
                ],
                "combinator": "and"
              }
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict",
                  "version": 2
                },
                "conditions": [
                  {
                    "id": "d0bf0e9a-81b2-4be3-b897-f8b1c405c5c0",
                    "leftValue": "={{ $json.message.successful_payment }}",
                    "rightValue": "",
                    "operator": {
                      "type": "object",
                      "operation": "exists",
                      "singleValue": true
                    }
                  }
                ],
                "combinator": "and"
              }
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict",
                  "version": 2
                },
                "conditions": [
                  {
                    "id": "6337ea59-4635-406e-89d4-62505dae4f35",
                    "leftValue": "={{ $json.callback_query }}",
                    "rightValue": "gpt",
                    "operator": {
                      "type": "object",
                      "operation": "exists",
                      "singleValue": true
                    }
                  }
                ],
                "combinator": "and"
              }
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict",
                  "version": 2
                },
                "conditions": [
                  {
                    "id": "d6fc6a81-7010-4119-9d91-c66d79e68ac8",
                    "leftValue": "={{ $json.callback_query }}",
                    "rightValue": "=flux",
                    "operator": {
                      "type": "object",
                      "operation": "notExists",
                      "singleValue": true
                    }
                  }
                ],
                "combinator": "and"
              }
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.switch",
      "typeVersion": 3.2,
      "position": [
        -1328,
        1920
      ],
      "id": "d8fd6414-7431-4c53-9815-32a6e54887bf",
      "name": "Switch_message_type"
    },
    {
      "parameters": {
        "rules": {
          "values": [
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict",
                  "version": 2
                },
                "conditions": [
                  {
                    "id": "6337ea59-4635-406e-89d4-62505dae4f35",
                    "leftValue": "={{ $json.message.web_app_data }}",
                    "rightValue": "gpt",
                    "operator": {
                      "type": "object",
                      "operation": "exists",
                      "singleValue": true
                    }
                  }
                ],
                "combinator": "and"
              }
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict",
                  "version": 2
                },
                "conditions": [
                  {
                    "id": "d6fc6a81-7010-4119-9d91-c66d79e68ac8",
                    "leftValue": "={{ $json.message.web_app_data }}",
                    "rightValue": "=flux",
                    "operator": {
                      "type": "object",
                      "operation": "notExists",
                      "singleValue": true
                    }
                  }
                ],
                "combinator": "and"
              }
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.switch",
      "typeVersion": 3.2,
      "position": [
        -320,
        2016
      ],
      "id": "878f23f7-8821-4e12-8edd-7034e1b4de40",
      "name": "Switch_web_app"
    },
    {
      "parameters": {
        "rules": {
          "values": [
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict",
                  "version": 2
                },
                "conditions": [
                  {
                    "id": "6337ea59-4635-406e-89d4-62505dae4f35",
                    "leftValue": "={{ $json.shouldSendMenu }}",
                    "rightValue": "gpt",
                    "operator": {
                      "type": "boolean",
                      "operation": "true",
                      "singleValue": true
                    }
                  }
                ],
                "combinator": "and"
              }
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict",
                  "version": 2
                },
                "conditions": [
                  {
                    "id": "d6fc6a81-7010-4119-9d91-c66d79e68ac8",
                    "leftValue": "={{ $json.shouldSendMenu }}",
                    "rightValue": "=flux",
                    "operator": {
                      "type": "boolean",
                      "operation": "false",
                      "singleValue": true
                    }
                  }
                ],
                "combinator": "and"
              }
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.switch",
      "typeVersion": 3.2,
      "position": [
        400,
        2016
      ],
      "id": "c7049219-c441-46c6-a96f-d06cd341662d",
      "name": "switch_reply_menu"
    },
    {
      "parameters": {
        "jsCode": "// FIRST FUNCTION: parse web_app_data & save settings (FLUX + SEEDANCE + SEEDREAM + VEO + SORA)\nconst sd = $getWorkflowStaticData('global');\nsd.flux      = sd.flux      || {}; // { [chatId]: { ar, ref, seed, quality, useRef, savedAt } }\nsd.seedance  = sd.seedance  || {}; // { [chatId]: { mode,tier,model,i2v_mode,resolution,ratio,duration,camerafixed,savedAt } }\nsd.seedream  = sd.seedream  || {}; // { [chatId]: { quality, ratio, dims_t2i:{w,h}, long_edge_i2i, savedAt } }\nsd.veo       = sd.veo       || {}; // { [chatId]: { model, ratio, mode, savedAt } }\nsd.sora      = sd.sora      || {}; // { [chatId]: { model, ratio, duration, savedAt } }  <-- NEW\n\nconst msg = $json.message || {};\nconst chatId = String(msg.chat?.id || $json.chat?.id || '');\nconst wad = msg.web_app_data?.data;   // JSON-строка из Telegram WebApp\n\nlet saved = false;\nlet kind  = null;   // 'flux' | 'seedance' | 'seedream' | 'veo' | 'sora'\nlet savedCfg = null;\nlet err = null;\n\nfunction clamp(n, lo, hi) { return Math.min(hi, Math.max(lo, n)); }\n\ntry {\n  if (chatId && wad) {\n    const raw = JSON.parse(wad);\n\n    // ======== FLUX ========\n    if (raw?.type === 'flux_settings') {\n      kind = 'flux';\n\n      const cleanAr = String(raw.ar ?? '1:1').replace(/\\s+/g, '');\n      const ar = /^\\d+:\\d+$/.test(cleanAr) ? cleanAr : '1:1';\n\n      let ref = Number(raw.ref);\n      if (!Number.isFinite(ref)) ref = 0.75;\n      ref = clamp(ref, 0, 1.2);\n\n      const seed = raw.random ? 0 : (Number(raw.seed) || 0);\n      const qNum = Number(raw.quality);\n      const bNum = Number(raw.batch);\n      const quality = [0,1,2].includes(qNum) ? qNum : 1;\n      const batch = [1,2,4].includes(bNum) ? bNum : 1;\n      \n      const useRef = Boolean(raw.useRef ?? raw.use_ref ?? false);\n\n      const cfg = { ar, ref, seed, quality, batch, useRef, savedAt: new Date().toISOString() };\n      sd.flux[chatId] = cfg;\n      saved = true;\n      savedCfg = cfg;\n    }\n\n    // ======== SEEDANCE ========\n    if (raw?.type === 'seedance_settings') {\n      kind = 'seedance';\n\n      const mode = /^(i2v|t2v)$/.test(String(raw.mode)) ? raw.mode : 'i2v';\n      const tier = /^(lite|pro)$/.test(String(raw.tier)) ? raw.tier : 'lite';\n\n      const i2v_mode = /^(first|last|first_last|reference)$/.test(String(raw.i2v_mode))\n        ? raw.i2v_mode : 'first';\n\n      const MODEL_IDS = {\n        lite: {\n          i2v: 'bytedance-seedance-1-0-lite-i2v-250428',\n          t2v: 'bytedance-seedance-1-0-lite-t2v-250428',\n        },\n        pro: {\n          i2v: null,\n          t2v: null,\n        }\n      };\n      const fallbackModel = MODEL_IDS?.[tier]?.[mode] || null;\n      const model = String(raw.model || fallbackModel || '');\n\n      const resolution = /^(480p|720p)$/i.test(String(raw.resolution)) ? raw.resolution.toLowerCase() : '480p';\n\n      const R = new Set(['auto','16:9','4:3','1:1','3:4','9:16','21:9']);\n      const ratio = R.has(String(raw.ratio)) ? String(raw.ratio) : 'auto';\n\n      let duration = Number(raw.duration);\n      duration = (duration === 5 || duration === 10) ? duration : 5;\n\n      const camerafixed = Boolean(raw.camerafixed);\n\n      const cfg = {\n        mode, tier, model,\n        i2v_mode,\n        resolution, ratio, duration, camerafixed,\n        savedAt: new Date().toISOString()\n      };\n      sd.seedance[chatId] = cfg;\n      saved = true;\n      savedCfg = cfg;\n    }\n\n    // ======== SEEDREAM (image gen) ========\n    if (raw?.type === 'seedream_settings') {\n      kind = 'seedream';\n\n      let quality = String(raw.quality || '2k').toLowerCase();\n      if (!['1k','2k','4k'].includes(quality)) quality = '2k';\n\n      const R2 = new Set(['1:1','4:3','3:4','16:9','9:16','3:2','2:3','21:9']);\n      let ratio = String(raw.ratio || '1:1');\n      if (!R2.has(ratio)) ratio = '1:1';\n\n      const BASE_2K = {\n        '1:1':  { w: 2048, h: 2048 },\n        '4:3':  { w: 2304, h: 1728 },\n        '3:4':  { w: 1728, h: 2304 },\n        '16:9': { w: 2560, h: 1440 },\n        '9:16': { w: 1440, h: 2560 },\n        '3:2':  { w: 2496, h: 1664 },\n        '2:3':  { w: 1664, h: 2496 },\n        '21:9': { w: 3024, h: 1296 },\n      };\n\n      const scale = (quality === '1k') ? 0.5 : (quality === '4k') ? 2 : 1;\n      const base = BASE_2K[ratio] || BASE_2K['1:1'];\n      const dims_t2i = { w: Math.round(base.w * scale), h: Math.round(base.h * scale) };\n      const long_edge_i2i = (quality === '1k') ? 1024 : (quality === '4k' ? 4096 : 2048);\n\n      const cfg = { quality, ratio, dims_t2i, long_edge_i2i, savedAt: new Date().toISOString() };\n      sd.seedream[chatId] = cfg;\n      saved = true;\n      savedCfg = cfg;\n    }\n\n    // ======== VEO (video gen) ========\n    if (raw?.type === 'veo_settings') {\n      kind = 'veo';\n\n      let model = String(raw.model || 'veo3.1').toLowerCase();\n      if (!/^veo/.test(model)) model = 'veo3.1';\n\n      let ratio = String(raw.ratio || '').trim();\n      if (!/^(16:9|9:16)$/.test(ratio)) ratio = 'auto';\n\n      let mode = String(raw.mode || 't2v');\n      if (!/^(t2v|i2v|first_last|reference)$/.test(mode)) mode = 't2v';\n\n      const cfg = { model, ratio, mode, savedAt: new Date().toISOString() };\n      sd.veo[chatId] = cfg;\n      saved = true;\n      savedCfg = cfg;\n    }\n\n    // ======== SORA (video gen) — NEW ========\n    if (raw?.type === 'sora_settings') {\n      kind = 'sora';\n\n      // model: sora-2-pro | sora-2-hd | sora-2\n      const ALLOWED = new Set(['sora-2-pro','sora-2-hd','sora-2']);\n      let model = String(raw.model || 'sora-2').toLowerCase();\n      if (!ALLOWED.has(model)) model = 'sora-2';\n\n      // ratio: 16:9 | 9:16\n      let ratio = String(raw.ratio || '16:9');\n      if (!/^(16:9|9:16)$/.test(ratio)) ratio = '16:9';\n\n      // duration: 4 | 8 | 12\n      let duration = Number(raw.duration);\n      duration = [4,8,12].includes(duration) ? duration : 8;\n\n      const cfg = { model, ratio, duration, savedAt: new Date().toISOString() };\n      sd.sora[chatId] = cfg;\n      saved = true;\n      savedCfg = cfg;\n    }\n  }\n} catch (e) {\n  err = String(e?.message || e);\n}\n\nreturn [{\n  json: {\n    chatId,\n    hasWebAppData: Boolean(wad),\n    saved,\n    kind,          // 'flux' | 'seedance' | 'seedream' | 'veo' | 'sora' | null\n    savedCfg,\n    errorMsg: err || null,\n    deleteChatId: chatId,\n    deleteMessageId: msg.message_id\n  }\n}];\n"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -96,
        1808
      ],
      "id": "bfc9c533-bb3d-465c-b2c4-e97efae0a3bf",
      "name": "parse web_app_data"
    },
    {
      "parameters": {
        "chatId": "={{ $json.chatId }}",
        "text": "={{ $json.lang === 'en' ? '🌐 Choose your language:' : '🌐 Выберите язык:' }}",
        "replyMarkup": "inlineKeyboard",
        "inlineKeyboard": {
          "rows": [
            {
              "row": {
                "buttons": [
                  {
                    "text": "Русский ",
                    "additionalFields": {
                      "callback_data": "setlang:ru"
                    }
                  },
                  {
                    "text": "English ",
                    "additionalFields": {
                      "callback_data": "setlang:en"
                    }
                  }
                ]
              }
            }
          ]
        },
        "additionalFields": {
          "appendAttribution": false
        }
      },
      "id": "42b24f4b-6d0b-4bf9-b679-15699f28dac9",
      "name": "lang_inline",
      "type": "n8n-nodes-base.telegram",
      "typeVersion": 1.2,
      "position": [
        2016,
        992
      ],
      "webhookId": "2b9cbcf8-d73a-4d63-ba56-8fac5109a862",
      "credentials": {
        "telegramApi": {
          "id": "ur7jSUPdiAaPVhCf",
          "name": "Geni AI"
        }
      }
    },
    {
      "parameters": {
        "operation": "deleteMessage",
        "chatId": "={{ $json.result.chat.id }}",
        "messageId": "={{ $('Switch1').item.json.deleteMessageId }}"
      },
      "id": "d7a6d677-9096-4f04-af9b-6cbf64816edb",
      "name": "delMes",
      "type": "n8n-nodes-base.telegram",
      "typeVersion": 1.2,
      "position": [
        2224,
        992
      ],
      "webhookId": "7e67278c-ca08-4b2b-ade0-5fbc6c1e9c14",
      "credentials": {
        "telegramApi": {
          "id": "ur7jSUPdiAaPVhCf",
          "name": "Geni AI"
        }
      },
      "onError": "continueRegularOutput"
    },
    {
      "parameters": {
        "operation": "deleteMessage",
        "chatId": "={{ $('Switch1').item.json.chatId }}",
        "messageId": "={{ $('Switch1').item.json.deleteMessageId }}"
      },
      "id": "6cff3a8a-d1ce-4931-a54d-84be2051dd0b",
      "name": "delMes1",
      "type": "n8n-nodes-base.telegram",
      "typeVersion": 1.2,
      "position": [
        2576,
        176
      ],
      "webhookId": "72e170d1-8ab7-402e-84e2-57ed36c830ef",
      "credentials": {
        "telegramApi": {
          "id": "ur7jSUPdiAaPVhCf",
          "name": "Geni AI"
        }
      }
    },
    {
      "parameters": {
        "operation": "deleteMessage",
        "chatId": "={{ $json.chatId }}",
        "messageId": "={{ $json.targetMsgId }}"
      },
      "id": "c5fc35e3-5029-49ff-bdbf-4e40b5ed2c5a",
      "name": "delMes_targetMsg",
      "type": "n8n-nodes-base.telegram",
      "typeVersion": 1.2,
      "position": [
        -448,
        -480
      ],
      "webhookId": "263a1d6e-2aab-45a0-ac0a-fd4f61fe227d",
      "credentials": {
        "telegramApi": {
          "id": "ur7jSUPdiAaPVhCf",
          "name": "Geni AI"
        }
      }
    },
    {
      "parameters": {
        "operation": "deleteMessage",
        "chatId": "={{ $json.chatId }}",
        "messageId": "={{ $json.keyboardMsgId }}"
      },
      "id": "78fbf151-84d7-4f87-9277-73c25acf66ae",
      "name": "delMes_keyboardMsg",
      "type": "n8n-nodes-base.telegram",
      "typeVersion": 1.2,
      "position": [
        -240,
        -480
      ],
      "webhookId": "bfdf4554-b218-4367-b05c-4a3a963e8692",
      "credentials": {
        "telegramApi": {
          "id": "ur7jSUPdiAaPVhCf",
          "name": "Geni AI"
        }
      }
    },
    {
      "parameters": {
        "operation": "deleteMessage",
        "chatId": "={{ $('message_reply_inline').item.json.chatId }}",
        "messageId": "={{ $('message_reply_inline').item.json.keyboardMsgId }}"
      },
      "id": "cdd2a43d-31ab-464e-970c-78ffcfbfa81f",
      "name": "delMes_keyboardMsg1",
      "type": "n8n-nodes-base.telegram",
      "typeVersion": 1.2,
      "position": [
        -240,
        32
      ],
      "webhookId": "e7190172-8910-40d7-8c25-51328f01d3d9",
      "credentials": {
        "telegramApi": {
          "id": "ur7jSUPdiAaPVhCf",
          "name": "Geni AI"
        }
      }
    },
    {
      "parameters": {
        "rules": {
          "values": [
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict",
                  "version": 2
                },
                "conditions": [
                  {
                    "id": "78542b6d-15c2-4809-935c-49d1e17dcc1e",
                    "leftValue": "={{ $json.account_action }}",
                    "rightValue": "balance",
                    "operator": {
                      "type": "string",
                      "operation": "equals",
                      "name": "filter.operator.equals"
                    }
                  }
                ],
                "combinator": "and"
              }
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict",
                  "version": 2
                },
                "conditions": [
                  {
                    "id": "dfbae9da-3cc7-488e-9f71-f21a4b9b4345",
                    "leftValue": "={{ $json.account_action }}",
                    "rightValue": "status",
                    "operator": {
                      "type": "string",
                      "operation": "equals",
                      "name": "filter.operator.equals"
                    }
                  }
                ],
                "combinator": "and"
              }
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict",
                  "version": 2
                },
                "conditions": [
                  {
                    "id": "6337ea59-4635-406e-89d4-62505dae4f35",
                    "leftValue": "={{ $json.account_action }}",
                    "rightValue": "buy_sub",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    }
                  }
                ],
                "combinator": "and"
              }
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict",
                  "version": 2
                },
                "conditions": [
                  {
                    "id": "d6fc6a81-7010-4119-9d91-c66d79e68ac8",
                    "leftValue": "={{ $json.account_action }}",
                    "rightValue": "=buy_credits",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    }
                  }
                ],
                "combinator": "and"
              }
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.switch",
      "typeVersion": 3.2,
      "position": [
        1760,
        304
      ],
      "id": "109d52d5-e6c4-4550-8072-79dd6e29ae30",
      "name": "Switch_lang_act"
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://api.telegram.org/bot8495842221:AAHBriXvzudyazB2f8z3ilGQ8A5ESdjjtl8/deleteMessage",
        "sendBody": true,
        "bodyParameters": {
          "parameters": [
            {
              "name": "chat_id",
              "value": "={{ $('menu').item.json.chatId }}"
            },
            {
              "name": "message_id",
              "value": "={{ $('menu').item.json.deleteMessageId }}"
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        816,
        1792
      ],
      "id": "21afacf0-2d14-4d4a-a3a3-db692b94eec7",
      "name": "deleteMessageMenu",
      "onError": "continueRegularOutput"
    },
    {
      "parameters": {
        "jsCode": "const sd = $getWorkflowStaticData('global');\nsd.userLang = sd.userLang || {};\n\n// Инлайн-колбэк от Telegram\nconst cq = $json.callback_query || {};\nconst dataRaw = cq.data;\nconst data = typeof dataRaw === 'string'\n  ? dataRaw.trim()\n  : String(dataRaw || '').trim();\n\nconst chatId = String(cq.message?.chat?.id || '');\nconst keyboardMsgId = cq.message?.message_id; // наше сообщение с инлайнами\n\nlet action = null;        // 'del_one' | 'del_all' | 'setlang' | 'buy_credits_pack'\nlet targetMsgId = null;   // для del_one\nlet pack_credits = null;  // для buy_credits_pack\nlet lang = sd.userLang[chatId] || null; // 'ru' | 'en', если уже выбран язык\n\n// ---------- разбор callback_data ----------\n// Форматы, которые сейчас используются:\n//  - \"del:12345\"      → удалить одно сообщение\n//  - \"del_all\"        → очистить все\n//  - \"setlang:ru|en\"  → смена языка\n//  - \"credits:200\"    → выбор пакета кредитов (200/500/1000)\n\nif (data.startsWith('del:')) {\n  // Удалить одно сообщение\n  action = 'del_one';\n  const idStr = data.split(':')[1];\n  const n = Number(idStr);\n  if (Number.isFinite(n)) targetMsgId = n;\n\n} else if (data === 'del_all') {\n  // Удалить все\n  action = 'del_all';\n\n} else if (data.startsWith('setlang:')) {\n  // Смена языка\n  action = 'setlang';\n  const tail = data.split(':')[1]?.trim().toLowerCase();\n  if (tail === 'ru' || tail === 'en') {\n    lang = tail;\n  }\n\n} else if (data.startsWith('credits:')) {\n  // Выбор пакета кредитов\n  // callback_data: \"credits:200\" | \"credits:500\" | \"credits:1000\"\n  action = 'buy_credits_pack';\n  const tail = data.split(':')[1]?.trim();\n  const n = Number(tail);\n  if (Number.isFinite(n) && n > 0) {\n    pack_credits = n;\n  }\n}\n\n// Валидируем язык: по умолчанию 'ru'\nif (!['ru', 'en'].includes(lang)) {\n  lang = 'ru';\n}\n\n// Подстраховка для del_one — если id не пришёл в callback_data,\n// попробуем взять из reply_to_message\nif (!targetMsgId && cq.message?.reply_to_message?.message_id) {\n  targetMsgId = cq.message.reply_to_message.message_id;\n}\n\nreturn [{\n  json: {\n    action,                // 'del_one' | 'del_all' | 'setlang' | 'buy_credits_pack'\n    lang,                  // язык (особенно важен при setlang)\n    chatId,\n    targetMsgId,           // для del_one\n    keyboardMsgId,         // id сообщения с инлайн-кнопками\n    callback_query_id: cq.id,\n    pack_credits           // для buy_credits_pack (200/500/1000 или null)\n  }\n}];\n"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -912,
        -192
      ],
      "id": "849ffd5b-8842-4b3a-83c4-7a6e85965b67",
      "name": "message_reply_inline"
    },
    {
      "parameters": {
        "rules": {
          "values": [
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict",
                  "version": 2
                },
                "conditions": [
                  {
                    "id": "6337ea59-4635-406e-89d4-62505dae4f35",
                    "leftValue": "={{ $json.action }}",
                    "rightValue": "del_one",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    }
                  }
                ],
                "combinator": "and"
              }
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict",
                  "version": 2
                },
                "conditions": [
                  {
                    "id": "d6fc6a81-7010-4119-9d91-c66d79e68ac8",
                    "leftValue": "={{ $json.action }}",
                    "rightValue": "=del_all",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    }
                  }
                ],
                "combinator": "and"
              }
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict",
                  "version": 2
                },
                "conditions": [
                  {
                    "id": "df33815a-03c8-4a3a-a25f-0a4a7e266f1a",
                    "leftValue": "={{ $json.action }}",
                    "rightValue": "setlang",
                    "operator": {
                      "type": "string",
                      "operation": "equals",
                      "name": "filter.operator.equals"
                    }
                  }
                ],
                "combinator": "and"
              }
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict",
                  "version": 2
                },
                "conditions": [
                  {
                    "id": "fed3019b-9611-47cc-969b-c87ddce299c7",
                    "leftValue": "={{ $json.action }}",
                    "rightValue": "buy_credits_pack",
                    "operator": {
                      "type": "string",
                      "operation": "equals",
                      "name": "filter.operator.equals"
                    }
                  }
                ],
                "combinator": "and"
              }
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.switch",
      "typeVersion": 3.2,
      "position": [
        -704,
        -224
      ],
      "id": "1ff60860-9caf-4e9c-8cf7-af9dc606b8f6",
      "name": "Switch_inline_act"
    },
    {
      "parameters": {
        "chatId": "={{ $('payment.parse_success').item.json.chatId }}",
        "text": "={{ $('payment.parse_success').item.json.ack.text }}",
        "additionalFields": {
          "appendAttribution": false,
          "parse_mode": "HTML"
        }
      },
      "id": "5713ca55-a40c-4c5e-a773-37ef474b0840",
      "name": "send_mes_payment",
      "type": "n8n-nodes-base.telegram",
      "typeVersion": 1.2,
      "position": [
        -384,
        -1072
      ],
      "webhookId": "c3fe8895-404b-4fa5-a0c8-46733c06592f",
      "credentials": {
        "telegramApi": {
          "id": "ur7jSUPdiAaPVhCf",
          "name": "Geni AI"
        }
      }
    },
    {
      "parameters": {
        "method": "POST",
        "url": "=https://bwbsclwdkighhzlyiman.supabase.co/rest/v1/rpc/set_user_lang",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "supabaseApi",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": " application/json"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={\n  \"p_tg_user_id\": {{ $json.chatId }},\n  \"p_lang\": \"{{ $json.lang }}\"\n}",
        "options": {}
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        -464,
        32
      ],
      "id": "8e6d0bcf-fa72-46fb-bf19-3aadda914ce2",
      "name": "SB_set_user_lang",
      "credentials": {
        "supabaseApi": {
          "id": "jGgpXKPYHiL193Rz",
          "name": "Supabase account"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "// n8n Code — Build items-to-delete (UNIFIED) — same chatId resolver as \"Clear ALL\"\n// Output: array of { json: { chatId, messageId } } in this order:\n//   1) sd.replyIndex[chatId] (+ keyboardMsgId if provided)\n//   2) sd.photoBucket[chatId].files[].message_id (+ targetMsgId if provided)\n//   3) keys of sd.photoIndex[chatId]\n\nconst sd = $getWorkflowStaticData('global');\n\n// Ensure unified stores (do NOT mutate here)\nsd.photoBucket = sd.photoBucket || {};   // { [chatId]: { files:[{file_id,message_id,...}], savedAt } }\nsd.photoIndex  = sd.photoIndex  || {};   // { [chatId]: { [messageId]: file_id } }\nsd.replyIndex  = sd.replyIndex  || {};   // { [chatId]: number[] }\n\n// Same chatId resolution as in your \"Clear ALL\" node\nconst first  = $input.first()?.json || {};\nconst msg    = $json.message || first.message || {};\nconst chatId = String(\n  $json.chatId ||\n  first.chatId ||\n  msg.chat?.id ||\n  $json.chat?.id ||\n  first.chat?.id ||\n  ''\n);\n\nif (!chatId) {\n  return []; // nothing to delete\n}\n\n// Optional context ids from current item\nconst keyboardMsgId = Number($json.keyboardMsgId ?? first.keyboardMsgId ?? 0);\nconst targetMsgId   = Number($json.targetMsgId   ?? first.targetMsgId   ?? 0);\n\n// Helpers\nconst isValidId = (n) => Number.isFinite(n) && n > 0;\nconst uniqOrdered = (arr) => {\n  const seen = new Set(); const out = [];\n  for (const n of arr) { const v = Number(n); if (isValidId(v) && !seen.has(v)) { seen.add(v); out.push(v); } }\n  return out;\n};\n\n// 1) inline replies (replyIndex)\nlet replyIds = Array.isArray(sd.replyIndex[chatId]) ? sd.replyIndex[chatId].slice() : [];\nif (keyboardMsgId) replyIds.push(keyboardMsgId);\nreplyIds = uniqOrdered(replyIds);\n\n// 2) photo message ids from bucket + index\nconst bucketIds = uniqOrdered((sd.photoBucket[chatId]?.files || []).map(x => x?.message_id));\nconst indexIds  = uniqOrdered(Object.keys(sd.photoIndex[chatId] || {}));\nlet photoIds = uniqOrdered([...bucketIds, ...indexIds]);\nif (targetMsgId) photoIds = uniqOrdered([...photoIds, targetMsgId]);\n\n// 3) final order: replies first, then photos\nconst all = [...replyIds, ...photoIds];\n\n// Map to items for Telegram deleteMessage\nreturn all.map(messageId => ({ json: { chatId, messageId } }));\n"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -464,
        -208
      ],
      "id": "f02cf1bb-4e6d-44bf-87f6-9f851e828d0b",
      "name": "Build items-to-delete"
    },
    {
      "parameters": {
        "jsCode": "// Delete ONE image (unified cache)\n// Inputs: chatId (string), targetMsgId (number), keyboardMsgId? (number)\n// Uses: sd.photoBucket, sd.photoIndex, sd.replyIndex\n\nconst sd = $getWorkflowStaticData('global');\nsd.photoBucket = sd.photoBucket || {};\nsd.photoIndex  = sd.photoIndex  || {};\nsd.replyIndex  = sd.replyIndex  || {};\n\nconst chatId        = String($json.chatId || '');\nconst targetMsgId   = Number($json.targetMsgId || 0);\nconst keyboardMsgId = Number($json.keyboardMsgId || 0);\n\nif (!chatId || !Number.isFinite(targetMsgId) || targetMsgId <= 0) {\n  return [{ json: { ok:false, reason:'missing_chat_or_target', chatId, targetMsgId } }];\n}\n\n// 1) убрать наш inline-reply из индекса (если есть)\nif (keyboardMsgId && Array.isArray(sd.replyIndex[chatId])) {\n  sd.replyIndex[chatId] = sd.replyIndex[chatId].filter(id => id !== keyboardMsgId);\n}\n\n// 2) найти file_id по sd.photoIndex либо по бакету\nconst bucket = sd.photoBucket[chatId];\nconst idxMap = sd.photoIndex[chatId] || {};\nlet removedFileId = idxMap[targetMsgId] || null;\n\nif (!removedFileId && bucket?.files?.length) {\n  const hit = bucket.files.find(x => Number(x?.message_id) === targetMsgId);\n  if (hit && hit.file_id) removedFileId = hit.file_id;\n}\n\n// 3) удалить из бакета по message_id (и на всякий — по совпадающему file_id)\nlet removed = false;\nif (bucket?.files?.length) {\n  const before = bucket.files.length;\n  bucket.files = bucket.files.filter(x => {\n    if (!x || typeof x !== 'object') return true;\n    if (Number(x.message_id) === targetMsgId) return false;\n    if (removedFileId && x.file_id === removedFileId) return false;\n    return true;\n  });\n  removed = bucket.files.length < before;\n  sd.photoBucket[chatId] = bucket;\n}\n\n// 4) удалить из индекса message_id→file_id (только эту запись)\nif (sd.photoIndex?.[chatId]?.[targetMsgId]) {\n  delete sd.photoIndex[chatId][targetMsgId];\n}\n\nreturn [{\n  json: {\n    ok: true,\n    chatId,\n    removed,\n    removed_file_id: removedFileId || null,\n    targetMsgId,\n    keyboardMsgId,\n    left_in_bucket: sd.photoBucket?.[chatId]?.files?.length || 0\n  }\n}];\n"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -48,
        -480
      ],
      "id": "068041d9-051b-4218-b482-b6af609ec05b",
      "name": "Delete ONE image from memory"
    },
    {
      "parameters": {
        "jsCode": "// n8n Code — Clear ALL unified caches for this chat\n// Targets ONLY the unified stores you decided to keep:\n//   sd.photoBucket[chatId]  — remove\n//   sd.photoIndex[chatId]   — remove\n//   sd.replyIndex[chatId]   — remove\n// Leaves sd.poll intact. No legacy/tool-specific caches touched.\n\nconst sd = $getWorkflowStaticData('global');\n\n// Ensure unified stores exist\nsd.photoBucket = sd.photoBucket || {};   // { [chatId]: { files:[{file_id,message_id,...}], savedAt } }\nsd.photoIndex  = sd.photoIndex  || {};   // { [chatId]: { [messageId]: file_id } }\nsd.replyIndex  = sd.replyIndex  || {};   // { [chatId]: number[] }\nsd.poll        = sd.poll        || {};\n\nconst first  = $input.first()?.json || {};\nconst msg    = $json.message || first.message || {};\nconst chatId = String(\n  $json.chatId ||\n  first.chatId ||\n  msg.chat?.id ||\n  $json.chat?.id ||\n  first.chat?.id ||\n  ''\n);\n\nlet lang = sd.userLang[chatId] || null;\n\nif (!chatId) {\n  return [{ json: { ok:false, reason:'no_chat' } }];\n}\n\n// Snapshot BEFORE\nconst before = {\n  bucket_files: sd.photoBucket?.[chatId]?.files?.length || 0,\n  index_keys:   sd.photoIndex?.[chatId] ? Object.keys(sd.photoIndex[chatId]).length : 0,\n  reply_len:    Array.isArray(sd.replyIndex?.[chatId]) ? sd.replyIndex[chatId].length : 0,\n};\n\n// (optional) collect ids we’re about to drop — handy for downstream debug/logging\nconst bucket_msg_ids = (sd.photoBucket?.[chatId]?.files || [])\n  .map(x => Number(x?.message_id))\n  .filter(n => Number.isFinite(n) && n > 0);\n\nconst index_msg_ids = sd.photoIndex?.[chatId]\n  ? Object.keys(sd.photoIndex[chatId]).map(n => Number(n)).filter(n => Number.isFinite(n) && n > 0)\n  : [];\n\nconst reply_msg_ids = Array.isArray(sd.replyIndex?.[chatId]) ? sd.replyIndex[chatId].slice() : [];\n\n// CLEAR unified caches\ndelete sd.photoBucket[chatId];\ndelete sd.photoIndex[chatId];\ndelete sd.replyIndex[chatId];\n\n// Snapshot AFTER\nconst after = {\n  bucket_files: sd.photoBucket?.[chatId]?.files?.length || 0,\n  index_keys:   sd.photoIndex?.[chatId] ? Object.keys(sd.photoIndex[chatId]).length : 0,\n  reply_len:    Array.isArray(sd.replyIndex?.[chatId]) ? sd.replyIndex[chatId].length : 0,\n};\n\nreturn [{\n  json: {\n    ok: true,\n    lang,\n    chatId,\n    before,\n    after,\n    deleted: {\n      bucket_msg_ids,\n      index_msg_ids,\n      reply_msg_ids,\n    }\n  }\n}];\n"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -240,
        -304
      ],
      "id": "0bbabd82-70ab-4f2b-b5b9-e87df2c4f8aa",
      "name": "Clear ALL buckets/indexes"
    },
    {
      "parameters": {
        "operation": "deleteMessage",
        "chatId": "={{ $json.chatId }}",
        "messageId": "={{ $json.messageId }}"
      },
      "id": "c1ae42f6-0d9f-4aed-b36e-8b10b7ad493d",
      "name": "delMes2",
      "type": "n8n-nodes-base.telegram",
      "typeVersion": 1.2,
      "position": [
        -240,
        -144
      ],
      "webhookId": "5becca08-9fa3-416f-8f0c-6e1a114ab9d6",
      "credentials": {
        "telegramApi": {
          "id": "ur7jSUPdiAaPVhCf",
          "name": "Geni AI"
        }
      },
      "onError": "continueRegularOutput"
    },
    {
      "parameters": {
        "chatId": "={{ $json.chatId }}",
        "text": "={{ $json.lang === 'en'\n  ? 'All uploaded photos have been cleared.'\n  : 'Все загруженные фото сброшены.'\n}}",
        "additionalFields": {
          "appendAttribution": false
        }
      },
      "id": "58a2d743-760c-4809-98bc-6f0cfdffc376",
      "name": "send_mes_clear",
      "type": "n8n-nodes-base.telegram",
      "typeVersion": 1.2,
      "position": [
        -48,
        -304
      ],
      "webhookId": "a46e0bb1-ac2c-43fc-b456-ef78749654ee",
      "credentials": {
        "telegramApi": {
          "id": "ur7jSUPdiAaPVhCf",
          "name": "Geni AI"
        }
      }
    },
    {
      "parameters": {
        "chatId": "={{ $('message_reply_inline').item.json.chatId }}",
        "text": "={{ $json.lang === 'en'\n    ? 'Language saved: English'\n    : 'Язык сохранён: Русский'\n  }}",
        "additionalFields": {
          "appendAttribution": false
        }
      },
      "id": "e372a0a1-50a9-4d07-a9a5-113c54b6dd3a",
      "name": "send_mes_lang_save",
      "type": "n8n-nodes-base.telegram",
      "typeVersion": 1.2,
      "position": [
        -48,
        32
      ],
      "webhookId": "6f095c52-076a-4679-baf4-721e13505ded",
      "credentials": {
        "telegramApi": {
          "id": "ur7jSUPdiAaPVhCf",
          "name": "Geni AI"
        }
      }
    },
    {
      "parameters": {
        "chatId": "={{ $json.tg_user_id }}",
        "text": "={{ $('Switch_lang_act').item.json.lang === 'en'\n    ? 'Current balance: ' + $json.balance + ' credits'\n    : 'Текущий баланс вашего аккаунта - ' + $json.balance + ' Кредитов'\n  }}",
        "additionalFields": {
          "appendAttribution": false
        }
      },
      "id": "1585199c-9601-4634-ad56-717376a72cf1",
      "name": "send_mes_balance",
      "type": "n8n-nodes-base.telegram",
      "typeVersion": 1.2,
      "position": [
        2336,
        48
      ],
      "webhookId": "92ceebf8-b19e-467b-b2ae-b534a4ea67c3",
      "credentials": {
        "telegramApi": {
          "id": "ur7jSUPdiAaPVhCf",
          "name": "Geni AI"
        }
      }
    },
    {
      "parameters": {
        "chatId": "={{ $json.chatId }}",
        "text": "={{ $json.lang === 'en'\n    ? 'Current plan: ' + ($json.plan || 'no active plan')\n    : 'Текущий статус вашего аккаунта - ' + ($json.plan || 'нет активного плана')\n  }}",
        "additionalFields": {
          "appendAttribution": false
        }
      },
      "id": "8edbccd4-7d68-417c-b90a-c65af0c90abb",
      "name": "send_mes_plan",
      "type": "n8n-nodes-base.telegram",
      "typeVersion": 1.2,
      "position": [
        2336,
        256
      ],
      "webhookId": "2a56c513-6002-466f-b3a6-4266d73a69f7",
      "credentials": {
        "telegramApi": {
          "id": "ur7jSUPdiAaPVhCf",
          "name": "Geni AI"
        }
      }
    },
    {
      "parameters": {
        "chatId": "={{ $('build_sub_invoice').item.json.chatId }}",
        "text": "={{ $('build_sub_invoice').item.json.pay_prompt_text }}",
        "replyMarkup": "inlineKeyboard",
        "inlineKeyboard": {
          "rows": [
            {
              "row": {
                "buttons": [
                  {
                    "text": "={{ $('build_sub_invoice').item.json.ui_texts.payStars }}",
                    "additionalFields": {
                      "url": "={{ $json.result }}"
                    }
                  }
                ]
              }
            },
            {
              "row": {
                "buttons": [
                  {
                    "text": "={{ $('build_sub_invoice').item.json.ui_texts.payCard }}",
                    "additionalFields": {
                      "url": "={{ $('build_sub_invoice').item.json.robokassa_url }}"
                    }
                  }
                ]
              }
            }
          ]
        },
        "additionalFields": {
          "appendAttribution": false
        }
      },
      "id": "593cdd9f-b109-4b66-ae63-359df315b547",
      "name": "send_mes_inline_buy_plan",
      "type": "n8n-nodes-base.telegram",
      "typeVersion": 1.2,
      "position": [
        2576,
        432
      ],
      "webhookId": "e2cbaf9c-00fb-4030-aa3c-6934b8c7a060",
      "credentials": {
        "telegramApi": {
          "id": "ur7jSUPdiAaPVhCf",
          "name": "Geni AI"
        }
      },
      "disabled": true
    },
    {
      "parameters": {
        "jsCode": "// n8n Code — Build \"choose credits pack\" message\n// Вход: $json с полями lang, chatId (из меню/роутера)\n// Выход: chatId, lang, text, reply_markup.inline_keyboard\n\nconst msg = $json.message || {};\nconst chatId = String(\n  $json.chatId ||\n  msg.chat?.id ||\n  $json.chat?.id ||\n  ''\n);\n\n// fallback, как в menu/аккаунте\nlet lang = (typeof $json.lang === 'string' && $json.lang.toLowerCase() === 'en') ? 'en' : 'ru';\n\n// Определяем тексты\nconst TEXT = {\n  ru: {\n    title: 'Выберите пакет кредитов:',\n    pack: (c) => `${c} Кредитов`,\n  },\n  en: {\n    title: 'Choose a credits pack:',\n    pack: (c) => `${c} credits`,\n  },\n};\n\nconst T = TEXT[lang] || TEXT.ru;\n\n// Описываем пакеты\nconst packs = [\n  { credits: 200 },\n  { credits: 500 },\n  { credits: 1000 },\n];\n\n// Строим inline_keyboard: по одному пакету в строке\nconst inline_keyboard = packs.map(p => [{\n  text: T.pack(p.credits),\n  callback_data: `credits:${p.credits}`,   // ВАЖНО: формат для Switch_inline_act\n}]);\n\nreturn [{\n  json: {\n    chatId,\n    lang,\n    text: T.title,\n    reply_markup: {\n      inline_keyboard,\n    },\n  },\n}];\n"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2032,
        640
      ],
      "id": "88c3a5a1-7380-49b0-8010-a59a26ce0c95",
      "name": "build_credits_packs"
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://api.telegram.org/bot8495842221:AAHBriXvzudyazB2f8z3ilGQ8A5ESdjjtl8/sendMessage",
        "sendBody": true,
        "bodyParameters": {
          "parameters": [
            {
              "name": "chat_id",
              "value": "={{ $json.chatId }}"
            },
            {
              "name": "text",
              "value": "={{ $json.text }}"
            },
            {
              "name": "parse_mode",
              "value": "HTML"
            },
            {
              "name": "reply_markup",
              "value": "={{ $json.reply_markup }}"
            },
            {
              "name": "disable_notification",
              "value": "=true"
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        2336,
        640
      ],
      "id": "80906b35-746c-4a76-ab24-0f859beba4f7",
      "name": "reply_markup1",
      "retryOnFail": true
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://api.telegram.org/bot8495842221:AAHBriXvzudyazB2f8z3ilGQ8A5ESdjjtl8/createInvoiceLink",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ $json.invoicePayload }}",
        "options": {}
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        -240,
        416
      ],
      "id": "96869bd8-cd3e-4b46-8d01-38d35024397a",
      "name": "createInvoiceLink_credits"
    },
    {
      "parameters": {
        "chatId": "={{ $('build_credits_invoice').item.json.chatId }}",
        "text": "={{ $('build_credits_invoice').item.json.ui.caption }}",
        "replyMarkup": "inlineKeyboard",
        "inlineKeyboard": {
          "rows": [
            {
              "row": {
                "buttons": [
                  {
                    "text": "={{ $('build_credits_invoice').item.json.ui.btn_stars }}",
                    "additionalFields": {
                      "url": "={{ $json.result }}"
                    }
                  }
                ]
              }
            },
            {
              "row": {
                "buttons": [
                  {
                    "text": "={{ $('build_credits_invoice').item.json.ui.btn_card }}",
                    "additionalFields": {
                      "url": "={{ $('create_payment_yookassa_credits').item.json.confirmation.confirmation_url }}"
                    }
                  }
                ]
              }
            }
          ]
        },
        "additionalFields": {
          "appendAttribution": false,
          "parse_mode": "HTML"
        }
      },
      "id": "f9ffaf79-5074-4352-aa5e-d13a34d3b6dd",
      "name": "send_mes_payment1",
      "type": "n8n-nodes-base.telegram",
      "typeVersion": 1.2,
      "position": [
        192,
        512
      ],
      "webhookId": "245ca41a-c6c4-4a94-bb1e-a918af8a165b",
      "credentials": {
        "telegramApi": {
          "id": "ur7jSUPdiAaPVhCf",
          "name": "Geni AI"
        }
      }
    },
    {
      "parameters": {
        "operation": "deleteMessage",
        "chatId": "={{ $json.chatId }}",
        "messageId": "={{ $json.keyboardMsgId }}"
      },
      "id": "50b3bb65-e6c0-46d4-b21e-fe33fae4335c",
      "name": "delMes_keyboardMsg2",
      "type": "n8n-nodes-base.telegram",
      "typeVersion": 1.2,
      "position": [
        -464,
        832
      ],
      "webhookId": "4dbc69a5-2493-4aac-baca-e4197736aca4",
      "credentials": {
        "telegramApi": {
          "id": "ur7jSUPdiAaPVhCf",
          "name": "Geni AI"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "// n8n Code — payment_store_prompt\n// Храним последнее \"оплатное\" сообщение с инлайнами в staticData,\n// чтобы потом удалить его после успешной оплаты.\n\nconst sd = $getWorkflowStaticData('global');\nsd.paymentPrompt = sd.paymentPrompt || {};   // { [chatId]: message_id }\n\n// Берём первый входящий item\nconst item = $input.first()?.json || $json || {};\nconst result = item.result || {};            // ответ Telegram sendMessage\nconst chat = result.chat || item.chat || {};\n\nconst chatId = String(\n  chat.id ||\n  item.chatId ||\n  item.db_event?.chat_id ||\n  ''\n);\n\nconst messageId = result.message_id;\n\nif (chatId && messageId) {\n  sd.paymentPrompt[chatId] = messageId;\n}\n\nreturn [{\n  json: {\n    chatId,\n    messageId,\n    stored: !!(chatId && messageId),\n  }\n}];\n"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        384,
        512
      ],
      "id": "39b6e6e3-f60f-475b-b8fb-3f7e33c15f98",
      "name": "payment_store_prompt"
    },
    {
      "parameters": {
        "jsCode": "// n8n Code — build_credits_invoice\n// Вход: chatId, lang, pack_credits (200/500/1000)\n// Выход: данные для Telegram Stars + данные для YooKassa (карта)\n\nconst sd = $getWorkflowStaticData('global');\nsd.userLang = sd.userLang || {};\n\nconst chatId = String($json.chatId || '');\nlet lang = (typeof $json.lang === 'string' && $json.lang.toLowerCase() === 'en') ? 'en' : 'ru';\nif (!lang && chatId && sd.userLang[chatId]) lang = sd.userLang[chatId];\n\n// выбранный пак\nconst credits = Number($json.pack_credits || 0);\nif (!Number.isFinite(credits) || credits <= 0) {\n  throw new Error('Invalid credits pack selected');\n}\n\n// Прайсинг:\n// amount_stars — цена в Stars (XTR для Telegram)\n// amount_rub   — цена в ₽ (для ЮKassa / карты)\nconst pricing = {\n  200: { amount_stars: 159, amount_rub: 199 },\n  500: { amount_stars: 389, amount_rub: 469 },\n  1000:{ amount_stars: 699, amount_rub: 899 },\n};\n\nconst baseCfg = pricing[credits] || pricing[200];\nconst amount_stars = Number(baseCfg.amount_stars) || 0;\nconst amount_rub   = Number(baseCfg.amount_rub)   || 0;\n\nif (!amount_stars || !amount_rub) {\n  throw new Error('Pricing not configured for this credits pack');\n}\n\n// Telegram Stars: минимальные единицы (как «копейки»)\n// Сейчас 1 Star = 1 единица для API (если что — потом поправим unit)\nconst unit = 1;\nconst total_units = amount_stars * unit;\n\n// Локализация текста\nconst T = {\n  ru: {\n    title:       `Пакет ${credits} Кредитов`,\n    description: `Покупка ${credits} Кредитов для аккаунта.`,\n    // Показываем и Stars, и ₽\n    pay_caption:\n      `Пакет: ${credits} Кредитов\\n` +\n      `Цена звёздами: ${amount_stars} Stars\\n` +\n      `Цена по карте: ${amount_rub} ₽`,\n    btn_stars:   '⭐ Оплатить звёздами',\n    btn_card:    '💳 Оплатить картой',\n    // Для ЮKassa\n    yk_description: `Пакет ${credits} кредитов, оплата картой.`,\n  },\n  en: {\n    title:       `${credits} Credits pack`,\n    description: `Purchase ${credits} credits for your account.`,\n    pay_caption:\n      `Pack: ${credits} credits\\n` +\n      `Price by Stars: ${amount_stars} Stars\\n` +\n      `Price by card: ${amount_rub} RUB`,\n    btn_stars:   '⭐ Pay with Stars',\n    btn_card:    '💳 Pay by card',\n    yk_description: `Pack of ${credits} credits, card payment.`,\n  },\n}[lang] || {\n  title:       `Пакет ${credits} Кредитов`,\n  description: `Покупка ${credits} Кредитов для аккаунта.`,\n  pay_caption:\n    `Пакет: ${credits} Кредитов\\n` +\n    `Цена звёздами: ${amount_stars} Stars\\n` +\n    `Цена по карте: ${amount_rub} ₽`,\n  btn_stars:   '⭐ Оплатить звёздами',\n  btn_card:    '💳 Оплатить картой',\n  yk_description: `Пакет ${credits} кредитов, оплата картой.`,\n};\n\n// payload под текущий payment.parse_success:\n// \"credits:200\", \"credits:500\", \"credits:1000\"\nconst payload = `credits:${credits}`;\n\n// === Telegram Stars invoice payload ===\nconst invoicePayload = {\n  chat_id: Number(chatId),\n  title: T.title,\n  description: T.description,\n  payload,\n  currency: 'XTR',\n  prices: [\n    { label: T.title, amount: total_units }\n  ]\n};\n\n// Пока заглушка под Робокассу (можно будет заменить на реальную ссылку)\nconst robokassa_url = 'https://example.com/robokassa/credits';\n\n// === YooKassa: подготовка данных ===\n\n// product_code — удобно использовать как \"credits_200\" / \"credits_500\" / \"credits_1000\"\nconst yk_product_code = `credits_${credits}`;\n\n// ЮKassa ждёт amount.value строкой с двумя знаками после запятой\nconst yk_amount_value = amount_rub.toFixed(2);\nconst yk_currency = 'RUB';\n\n// Описание платежа для ЮKassa\nconst yk_description = T.yk_description;\n\n// metadata — то, что вернётся в вебхуке (payment.succeeded)\nconst yk_metadata = {\n  tg_user_id: chatId,\n  credits,\n  product_code: yk_product_code,\n  source: 'telegram-bot',\n  lang,\n};\n\nreturn [{\n  json: {\n    chatId,\n    lang,\n    credits,\n\n    // Stars\n    price_stars: amount_stars,\n\n    // Рубли (для карты / ЮKassa)\n    price_rub: amount_rub,\n\n    // Telegram Stars invoice\n    invoicePayload,\n    robokassa_url,\n\n    // UI для текущих сообщений\n    ui: {\n      caption:   T.pay_caption,\n      btn_stars: T.btn_stars,\n      btn_card:  T.btn_card,\n    },\n\n    // YooKassa fields\n    yk_product_code,\n    yk_amount_value,\n    yk_currency,\n    yk_description,\n    yk_metadata,\n  }\n}];\n"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -464,
        512
      ],
      "id": "ceb2314c-fd38-45fe-9ae5-f4a989e2a37f",
      "name": "build_credits_invoice"
    },
    {
      "parameters": {
        "jsCode": "// n8n Code — payment_pick_prompt_to_delete\n// Забираем сохранённый message_id инлайна оплаты и готовим данные для deleteMessage.\n\nconst sd = $getWorkflowStaticData('global');\nsd.paymentPrompt = sd.paymentPrompt || {};   // { [chatId]: message_id }\n\n// В этом месте у нас JSON от payment.parse_success / apply_payment\nconst src = $json || {};\nconst chatId = String(\n  src.chatId ||\n  src.db_event?.chat_id ||\n  ''\n);\n\nlet delete_message_id = null;\n\nif (chatId && sd.paymentPrompt[chatId]) {\n  delete_message_id = sd.paymentPrompt[chatId];\n  // одноразово: после удаления не храним\n  delete sd.paymentPrompt[chatId];\n}\n\nreturn [{\n  json: {\n    ...src,\n    delete_chat_id: chatId || null,\n    delete_message_id: delete_message_id,\n  }\n}];\n"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -640,
        -864
      ],
      "id": "94f16d79-e687-470c-8dae-893f6db39790",
      "name": "payment_pick_prompt_to_delete"
    },
    {
      "parameters": {
        "operation": "deleteMessage",
        "chatId": "={{ $json.chatId }}",
        "messageId": "={{ $json.delete_message_id }}"
      },
      "id": "7efa892e-154f-43c1-b314-27d7710671df",
      "name": "delMes3",
      "type": "n8n-nodes-base.telegram",
      "typeVersion": 1.2,
      "position": [
        -384,
        -864
      ],
      "webhookId": "cb8798c8-d243-4bc9-bf94-f81f77c99c14",
      "credentials": {
        "telegramApi": {
          "id": "ur7jSUPdiAaPVhCf",
          "name": "Geni AI"
        }
      }
    },
    {
      "parameters": {
        "method": "POST",
        "url": "=https://bwbsclwdkighhzlyiman.supabase.co/rest/v1/rpc/wallet_grant_welcome_once",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "supabaseApi",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": " application/json"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={\n  \"p_tg_user_id\": \"{{ $('ensure_user').item.json.tg_user_id }}\",\n  \"p_tg_username\": \"{{ $('ensure_user').item.json.tg_username }}\",\n  \"p_amount\":      10,\n  \"p_lang\":        \"{{ $json.lang || 'ru' }}\"\n}\n",
        "options": {}
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        816,
        1248
      ],
      "id": "3e1098f2-702c-4209-84a2-d91ba85747c9",
      "name": "welcome_once",
      "retryOnFail": true,
      "credentials": {
        "supabaseApi": {
          "id": "jGgpXKPYHiL193Rz",
          "name": "Supabase account"
        }
      }
    },
    {
      "parameters": {
        "rules": {
          "values": [
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict",
                  "version": 2
                },
                "conditions": [
                  {
                    "id": "6337ea59-4635-406e-89d4-62505dae4f35",
                    "leftValue": "={{ $json.is_start }}",
                    "rightValue": "gpt",
                    "operator": {
                      "type": "boolean",
                      "operation": "true",
                      "singleValue": true
                    }
                  }
                ],
                "combinator": "and"
              }
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict",
                  "version": 2
                },
                "conditions": [
                  {
                    "id": "d6fc6a81-7010-4119-9d91-c66d79e68ac8",
                    "leftValue": "={{ $json.is_start }}",
                    "rightValue": "=flux",
                    "operator": {
                      "type": "boolean",
                      "operation": "false",
                      "singleValue": true
                    }
                  }
                ],
                "combinator": "and"
              }
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.switch",
      "typeVersion": 3.2,
      "position": [
        112,
        2016
      ],
      "id": "d5e4be4b-38b1-49ba-8644-30002f28a152",
      "name": "switch_start"
    },
    {
      "parameters": {
        "rules": {
          "values": [
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict",
                  "version": 2
                },
                "conditions": [
                  {
                    "id": "6337ea59-4635-406e-89d4-62505dae4f35",
                    "leftValue": "={{ $json.welcome_applied }}",
                    "rightValue": "gpt",
                    "operator": {
                      "type": "boolean",
                      "operation": "true",
                      "singleValue": true
                    }
                  }
                ],
                "combinator": "and"
              }
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict",
                  "version": 2
                },
                "conditions": [
                  {
                    "id": "d6fc6a81-7010-4119-9d91-c66d79e68ac8",
                    "leftValue": "={{ $json.welcome_applied }}",
                    "rightValue": "=flux",
                    "operator": {
                      "type": "boolean",
                      "operation": "false",
                      "singleValue": true
                    }
                  }
                ],
                "combinator": "and"
              }
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.switch",
      "typeVersion": 3.2,
      "position": [
        1024,
        1248
      ],
      "id": "74d71777-4e15-4326-b5be-3ea691b0b6a6",
      "name": "switch_welcome"
    },
    {
      "parameters": {
        "chatId": "={{ $('ensure_user').item.json.tg_user_id }}",
        "text": "={{ $('ensure_user').item.json.lang === 'en'\n  ? '🎁 We have credited 10 welcome credits to your account.'\n  : '🎁 Мы начислили вам 10 приветственных Кредитов.'\n}}",
        "additionalFields": {
          "appendAttribution": false
        }
      },
      "id": "765d90e2-2da4-4a68-9f89-767f2e1ea456",
      "name": "send_wel_cred",
      "type": "n8n-nodes-base.telegram",
      "typeVersion": 1.2,
      "position": [
        1232,
        1248
      ],
      "webhookId": "a2aae37a-1bcb-481d-adcf-d43a7b4b27a6",
      "credentials": {
        "telegramApi": {
          "id": "ur7jSUPdiAaPVhCf",
          "name": "Geni AI"
        }
      }
    },
    {
      "parameters": {
        "chatId": "={{ $('switch_start').item.json.chatId }}",
        "text": "={{ $('switch_start').item.json.lang === 'en'\n  ? '🫡 Welcome to GENI AI!'\n  : '🫡 Приветствую в GENI AI!'\n}}\n{{ $json.lang === 'en' ? '🌐 Choose your language:' : '🌐 Выберите язык:' }}",
        "replyMarkup": "inlineKeyboard",
        "inlineKeyboard": {
          "rows": [
            {
              "row": {
                "buttons": [
                  {
                    "text": "Русский",
                    "additionalFields": {
                      "callback_data": "setlang:ru"
                    }
                  }
                ]
              }
            },
            {
              "row": {
                "buttons": [
                  {
                    "text": "English ",
                    "additionalFields": {
                      "callback_data": "setlang:en"
                    }
                  }
                ]
              }
            }
          ]
        },
        "additionalFields": {
          "appendAttribution": false
        }
      },
      "id": "70f05f2c-cfc0-48e8-8945-9d67c3acdd03",
      "name": "send_start_mes",
      "type": "n8n-nodes-base.telegram",
      "typeVersion": 1.2,
      "position": [
        816,
        1040
      ],
      "webhookId": "1969a35d-d09b-43a2-9df5-891be378c6cf",
      "credentials": {
        "telegramApi": {
          "id": "ur7jSUPdiAaPVhCf",
          "name": "Geni AI"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "// lang_to_home_after_set\n// Генерируем \"фейковое\" текстовое сообщение `/menu`,\n// чтобы прогнать его через обычный роутинг (Switch_message_type → ensure_user → menu → reply_markup).\n\nconst chatId = String($json.chatId || $input.first().json.result.chat.id);\nconst lang   = typeof $('SB_set_user_lang').first().json.lang === 'string' ? $('SB_set_user_lang').first().json.lang.toLowerCase() : null;\n\nif (!chatId) {\n  // На всякий случай, если вдруг что-то пошло не так — просто ничего не делаем\n  return [];\n}\n\nconst now = Math.floor(Date.now() / 1000);\n\nreturn [{\n  json: {\n    // Эмулируем update.message, как будто Telegram прислал /menu\n    message: {\n      message_id: 0,              // фиктивный id — он нужен только для совместимости\n      from: {\n        id: Number(chatId) || chatId,\n        is_bot: false,\n        language_code: lang || 'ru',\n      },\n      chat: {\n        id: Number(chatId) || chatId,\n        type: 'private',\n      },\n      date: now,\n      text: '/menu',\n      entities: [\n        {\n          offset: 0,\n          length: 5,\n          type: 'bot_command',\n        },\n      ],\n    },\n    // Дополнительно можно пробросить lang, но ensure_user уже вернёт актуальный\n    lang: lang || undefined,\n  },\n}];\n"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        160,
        32
      ],
      "id": "5b35d56b-e756-40f6-b8fa-175fea97c464",
      "name": "lang_to_home_after_set"
    },
    {
      "parameters": {
        "operation": "deleteMessage",
        "chatId": "={{ $json.result.chat.id }}",
        "messageId": "={{ $('Switch1').item.json.deleteMessageId }}"
      },
      "id": "bbfccd08-7ba1-426e-ab8f-331276322a8c",
      "name": "delMes4",
      "type": "n8n-nodes-base.telegram",
      "typeVersion": 1.2,
      "position": [
        2224,
        1264
      ],
      "webhookId": "5af3ec5f-9501-4891-bb28-8d52e321d7cc",
      "credentials": {
        "telegramApi": {
          "id": "ur7jSUPdiAaPVhCf",
          "name": "Geni AI"
        }
      },
      "onError": "continueRegularOutput"
    },
    {
      "parameters": {
        "jsCode": "// lang_to_home_after_set\n// Генерируем \"фейковое\" текстовое сообщение `/menu`,\n// чтобы прогнать его через обычный роутинг (Switch_message_type → ensure_user → menu → \nconst chatId = String($('Switch1').first().json.chatId);\nconst lang   = typeof $('Switch1').first().json.lang === 'string' ? $('Switch1').first().json.lang.toLowerCase() : null;\n\nif (!chatId) {\n  // На всякий случай, если вдруг что-то пошло не так — просто ничего не делаем\n  return [];\n}\n\nconst now = Math.floor(Date.now() / 1000);\n\nreturn [{\n  json: {\n    // Эмулируем update.message, как будто Telegram прислал /menu\n    message: {\n      message_id: 0,              // фиктивный id — он нужен только для совместимости\n      from: {\n        id: Number(chatId) || chatId,\n        is_bot: false,\n        language_code: lang || 'ru',\n      },\n      chat: {\n        id: Number(chatId) || chatId,\n        type: 'private',\n      },\n      date: now,\n      text: '/menu',\n      entities: [\n        {\n          offset: 0,\n          length: 5,\n          type: 'bot_command',\n        },\n      ],\n    },\n    // Дополнительно можно пробросить lang, но ensure_user уже вернёт актуальный\n    lang: lang || undefined,\n  },\n}];\n"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2464,
        1264
      ],
      "id": "98ba3ef7-444e-46e9-b1f2-73d9d293c3bf",
      "name": "lang_to_home_after_set1"
    },
    {
      "parameters": {
        "chatId": "={{ $json.chatId }}",
        "text": "={{ $json.lang === 'en'\n  ? '📘 How to use Geni AI\\n\\n' +\n    '1. Open the menu and choose 🖼 Image → ⚡️ FLUX.\\n' +\n    '2. Send a text prompt (and optionally a reference photo).\\n' +\n    '3. The bot will generate images and spend credits for each request.\\n\\n' +\n    '💰 Balance & credits:\\n' +\n    '- Use 👤 Account → 💰 Balance to see your credits.\\n' +\n    '- Use 👤 Account → ⚡️ Buy credits to top up.\\n\\n' +\n    'If you run out of credits, the bot will show how to buy more.\\n' +\n    'If you need help text to me @GRIGORIY_VOYAKIN.'\n  : '📘 Как пользоваться Geni AI\\n\\n' +\n    '1. Откройте меню и выберите 🖼 Image → ⚡️ FLUX.\\n' +\n    '2. Отправьте текстовый промпт (можно добавить референс-фото).\\n' +\n    '3. Бот сгенерирует изображения и спишет Кредиты за запрос.\\n\\n' +\n    '💰 Баланс и Кредиты:\\n' +\n    '- В разделе 👤 Аккаунт → 💰 Баланс можно посмотреть остаток.\\n' +\n    '- В 👤 Аккаунт → ⚡️ Купить кредиты пополнить счёт.\\n\\n' +\n    'Если Кредитов не хватит, бот покажет, как их докупить.\\n' +\n    'Если нужна помощь, напишите мне @GRIGORIY_VOYAKIN.'\n}}",
        "additionalFields": {
          "appendAttribution": false,
          "parse_mode": "HTML"
        }
      },
      "id": "80a83edf-7382-4d9d-8c31-f491521d772b",
      "name": "Instruct_mes",
      "type": "n8n-nodes-base.telegram",
      "typeVersion": 1.2,
      "position": [
        2016,
        1264
      ],
      "webhookId": "c063a8aa-5e3d-4f7f-9969-213d4a97cb49",
      "credentials": {
        "telegramApi": {
          "id": "ur7jSUPdiAaPVhCf",
          "name": "Geni AI"
        }
      }
    },
    {
      "parameters": {
        "rules": {
          "values": [
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict",
                  "version": 2
                },
                "conditions": [
                  {
                    "id": "6337ea59-4635-406e-89d4-62505dae4f35",
                    "leftValue": "={{ $('switch_reply_menu').item.json.homeInlineShow }}",
                    "rightValue": "gpt",
                    "operator": {
                      "type": "boolean",
                      "operation": "true",
                      "singleValue": true
                    }
                  }
                ],
                "combinator": "and"
              }
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict",
                  "version": 2
                },
                "conditions": [
                  {
                    "id": "d6fc6a81-7010-4119-9d91-c66d79e68ac8",
                    "leftValue": "={{ $('switch_reply_menu').item.json.homeInlineShow }}",
                    "rightValue": "=flux",
                    "operator": {
                      "type": "boolean",
                      "operation": "false",
                      "singleValue": true
                    }
                  }
                ],
                "combinator": "and"
              }
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.switch",
      "typeVersion": 3.2,
      "position": [
        624,
        1568
      ],
      "id": "450f61d7-17f9-4f05-be64-cadd2feaecf7",
      "name": "switch_home_inline",
      "disabled": true
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://api.telegram.org/bot8495842221:AAHBriXvzudyazB2f8z3ilGQ8A5ESdjjtl8/sendMessage",
        "sendBody": true,
        "bodyParameters": {
          "parameters": [
            {
              "name": "chat_id",
              "value": "={{ $('switch_reply_menu').item.json.chatId }}"
            },
            {
              "name": "text",
              "value": "={{ $('switch_reply_menu').item.json.homeInlineText }}"
            },
            {
              "name": "parse_mode",
              "value": "HTML"
            },
            {
              "name": "reply_markup",
              "value": "={{ $('switch_reply_menu').item.json.homeInlineMarkup }}"
            },
            {
              "name": "disable_notification",
              "value": "={{ $('switch_reply_menu').item.json.homeInlineShow }}"
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        816,
        1568
      ],
      "id": "8133c5ce-d354-46fd-b97f-ee56e2787aa7",
      "name": "send_home_inline",
      "retryOnFail": true,
      "disabled": true
    },
    {
      "parameters": {
        "mode": "combine",
        "combineBy": "combineByPosition",
        "options": {}
      },
      "type": "n8n-nodes-base.merge",
      "typeVersion": 3.2,
      "position": [
        -16,
        512
      ],
      "id": "8d564289-84e6-4048-9815-87c5901d4763",
      "name": "Merge"
    },
    {
      "parameters": {
        "method": "POST",
        "url": "=https://bwbsclwdkighhzlyiman.supabase.co/rest/v1/payment_messages",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "supabaseApi",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": " application/json"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={\n  \"provider\": \"yookassa\",\n  \"charge_id\": \"{{ $('create_payment_yookassa_credits').item.json.id }}\",\n  \"chat_id\": {{ $json.result.chat.id }},\n  \"message_id\": {{ $json.result.message_id }}\n}",
        "options": {}
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        384,
        720
      ],
      "id": "d8aa30d1-3f09-4ebe-8189-acff9d636cbe",
      "name": "store_payment_message",
      "retryOnFail": true,
      "credentials": {
        "supabaseApi": {
          "id": "jGgpXKPYHiL193Rz",
          "name": "Supabase account"
        }
      }
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://api.yookassa.ru/v3/payments",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpBasicAuth",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Idempotence-Key",
              "value": "={{ $json.chatId + ':' + $json.credits + ':' + Date.now() }}"
            },
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={\n  \"amount\": {\n    \"value\": \"{{ $json.price_rub }}\",\n    \"currency\": \"RUB\"\n  },\n  \"capture\": true,\n  \"confirmation\": {\n    \"type\": \"redirect\",\n    \"return_url\": \"https://t.me/Geni_AI_bot\"\n  },\n  \"description\": \"{{ `Пакет ${$json.credits} кредитов. TG: ${$json.chatId}` }}\",\n  \"metadata\": {\n    \"tg_chat_id\": \"{{ $json.chatId }}\",\n    \"product\": \"credits\",\n    \"credits\": \"{{ $json.credits }}\",\n    \"lang\": \"{{ $json.yk_metadata.lang }}\"\n  },\n  \"receipt\": {\n    \"customer\": {\n      \"email\": \"grigoriyvoyakinwork@gmail.com\"\n    },\n    \"items\": [\n      {\n        \"description\": \"{{ `Пакет ${$json.credits} кредитов` }}\",\n        \"quantity\": 1,\n        \"amount\": {\n          \"value\": \"{{ $json.price_rub }}\",\n          \"currency\": \"RUB\"\n        },\n        \"vat_code\": 1\n      }\n    ]\n  }\n}\n",
        "options": {}
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        -240,
        640
      ],
      "id": "200b1c87-9d9b-454c-97a4-043c941bfd85",
      "name": "create_payment_yookassa_credits",
      "credentials": {
        "httpBasicAuth": {
          "id": "eoZ95FIg2ZXlecAm",
          "name": "yookassa_prod"
        }
      }
    },
    {
      "parameters": {
        "method": "POST",
        "url": "=https://bwbsclwdkighhzlyiman.supabase.co/rest/v1/rpc/get_user_id_and_balance",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "supabaseApi",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": " application/json"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={\n  \"p_tg_user_id\": \"{{ $json.chatId }}\"\n}\n",
        "options": {}
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        2032,
        48
      ],
      "id": "f30d1b6f-58db-44e1-bb7f-1ea561dc249b",
      "name": "id_and_balance",
      "retryOnFail": true,
      "credentials": {
        "supabaseApi": {
          "id": "jGgpXKPYHiL193Rz",
          "name": "Supabase account"
        }
      }
    },
    {
      "parameters": {
        "method": "POST",
        "url": "=https://bwbsclwdkighhzlyiman.supabase.co/rest/v1/rpc/ensure_user",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "supabaseApi",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": " application/json"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={\n  \"p_tg_user_id\": \"{{ $json.chatId }}\",\n  \"p_tg_username\": \"{{ $('Telegram Trigger1').item.json.message?.from?.username || '' }}\"\n}\n",
        "options": {}
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        624,
        1184
      ],
      "id": "ff277c5c-10bc-4522-a6f7-63b675ba9e58",
      "name": "ensure_user",
      "retryOnFail": true,
      "credentials": {
        "supabaseApi": {
          "id": "jGgpXKPYHiL193Rz",
          "name": "Supabase account"
        }
      }
    },
    {
      "parameters": {
        "rules": {
          "values": [
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict",
                  "version": 2
                },
                "conditions": [
                  {
                    "id": "6337ea59-4635-406e-89d4-62505dae4f35",
                    "leftValue": "={{ $json.action }}",
                    "rightValue": "STORED",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    }
                  }
                ],
                "combinator": "and"
              }
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict",
                  "version": 2
                },
                "conditions": [
                  {
                    "id": "d6fc6a81-7010-4119-9d91-c66d79e68ac8",
                    "leftValue": "={{ $json.action }}",
                    "rightValue": "=GENERATE",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    }
                  }
                ],
                "combinator": "and"
              }
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict",
                  "version": 2
                },
                "conditions": [
                  {
                    "id": "9cff2c53-bb3e-49bf-9cfe-53963eac8978",
                    "leftValue": "={{ $json.action }}",
                    "rightValue": "CLEARED",
                    "operator": {
                      "type": "string",
                      "operation": "equals",
                      "name": "filter.operator.equals"
                    }
                  }
                ],
                "combinator": "and"
              }
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict",
                  "version": 2
                },
                "conditions": [
                  {
                    "id": "39ccebaf-cc95-4d0b-954f-71fab6178691",
                    "leftValue": "={{ $json.action }}",
                    "rightValue": "NOOP ",
                    "operator": {
                      "type": "string",
                      "operation": "equals",
                      "name": "filter.operator.equals"
                    }
                  }
                ],
                "combinator": "and"
              }
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict",
                  "version": 2
                },
                "conditions": [
                  {
                    "id": "a9f7ce9b-cd18-4138-83f1-8cb70c4643ed",
                    "leftValue": "={{ $json.action }}",
                    "rightValue": "REMIND",
                    "operator": {
                      "type": "string",
                      "operation": "equals",
                      "name": "filter.operator.equals"
                    }
                  }
                ],
                "combinator": "and"
              }
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.switch",
      "typeVersion": 3.2,
      "position": [
        2032,
        2224
      ],
      "id": "d13fd2f7-d37a-42d1-b4bb-b2748182316c",
      "name": "Switch2"
    },
    {
      "parameters": {
        "chatId": "={{ $json.chatId }}",
        "text": "={{ $json.lang === 'en'\n  ? 'Send a photo or a prompt for generation.'\n  : 'Пришлите фото или промпт для генерации.'\n}}",
        "additionalFields": {
          "appendAttribution": false
        }
      },
      "id": "06fdf684-8a54-40e4-b5b3-d2c2e1d2af36",
      "name": "Telegram18",
      "type": "n8n-nodes-base.telegram",
      "typeVersion": 1.2,
      "position": [
        2304,
        2704
      ],
      "webhookId": "192bb390-c119-4d8f-b8cc-bbfe51bc0372",
      "credentials": {
        "telegramApi": {
          "id": "ur7jSUPdiAaPVhCf",
          "name": "Geni AI"
        }
      }
    },
    {
      "parameters": {
        "chatId": "={{ $json.chatId }}",
        "text": "={{ $json.lang === 'en'\n  ? 'All uploaded photos have been cleared.'\n  : 'Все загруженные фото сброшены.'\n}}",
        "additionalFields": {
          "appendAttribution": false
        }
      },
      "id": "deea1c46-34cc-4516-8998-197702f9353f",
      "name": "Telegram19",
      "type": "n8n-nodes-base.telegram",
      "typeVersion": 1.2,
      "position": [
        2304,
        2512
      ],
      "webhookId": "3cea3fd1-dd4f-4d78-9b0e-200130d147c1",
      "credentials": {
        "telegramApi": {
          "id": "ur7jSUPdiAaPVhCf",
          "name": "Geni AI"
        }
      }
    },
    {
      "parameters": {
        "chatId": "={{ $json.chatId }}",
        "text": "={{ $json.lang === 'en'\n  ? `${$json.reply_text}\\n⚡ Generation cost: ${$json.price} credits.\\nTap a button to delete.`\n  : `${$json.reply_text}\\n⚡Стоимость генерации: ${$json.price} кредитов.\\nНажмите кнопку, чтобы удалить.`\n}}",
        "replyMarkup": "inlineKeyboard",
        "inlineKeyboard": {
          "rows": [
            {
              "row": {
                "buttons": [
                  {
                    "text": "={{ $json.lang === 'en'\n  ? '🗑 Delete this photo'\n  : '🗑 Удалить это фото'\n}}",
                    "additionalFields": {
                      "callback_data": "del:${$json.photo_message_id}"
                    }
                  }
                ]
              }
            },
            {
              "row": {
                "buttons": [
                  {
                    "text": "={{ $json.lang === 'en'\n  ? '🧺 Clear all'\n  : '🧺 Очистить все'\n}}",
                    "additionalFields": {
                      "callback_data": "del_all"
                    }
                  }
                ]
              }
            }
          ]
        },
        "additionalFields": {
          "appendAttribution": false,
          "reply_to_message_id": "={{ $json.reply_to_message_id }}"
        }
      },
      "id": "72a12cdd-2b6e-41a4-967f-a1799ccbdfe3",
      "name": "Telegram20",
      "type": "n8n-nodes-base.telegram",
      "typeVersion": 1.2,
      "position": [
        2480,
        2080
      ],
      "webhookId": "6969c97f-2c91-44e1-ae46-426d11c99897",
      "credentials": {
        "telegramApi": {
          "id": "ur7jSUPdiAaPVhCf",
          "name": "Geni AI"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "const sd = $getWorkflowStaticData('global');\nsd.photoIndex  = sd.photoIndex  || {}; // { [chatId]: { [messageId]: file_id } }\n\nconst chatId = String($json.chatId || '');\nconst msgId  = Number($json.photo_message_id ?? $json.deleteMessageId ?? 0);\nconst fileId = $json.file_id || $json.photo_file_id || $json.primary_file?.file_id || null;\n\n\nif (chatId && msgId && fileId) {\n  sd.photoIndex[chatId] = sd.photoIndex[chatId] || {};\n  sd.photoIndex[chatId][msgId] = fileId;\n}\n\nreturn [{ json: $json }];\n"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2304,
        2080
      ],
      "id": "6a4b738a-17f0-40fb-b913-75b0cc157b72",
      "name": "Code15"
    },
    {
      "parameters": {
        "jsCode": "const sd = $getWorkflowStaticData('global');\nsd.replyIndex = sd.replyIndex || {}; // { [chatId]: number[] }\n\nconst chatId = String($json.chatId || $json.result?.chat?.id || '');\nconst sentId =\n  Number($json.result?.message_id) ||\n  Number($json.result?.message?.message_id) ||\n  Number($json.message_id) || 0;\n\nif (chatId && sentId) {\n  sd.replyIndex[chatId] = sd.replyIndex[chatId] || [];\n  if (!sd.replyIndex[chatId].includes(sentId)) sd.replyIndex[chatId].push(sentId);\n}\n\nreturn [{ json: $json }];\n"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2656,
        2080
      ],
      "id": "a0a675a4-7296-4e10-b9fe-cdbba0206956",
      "name": "Code23"
    },
    {
      "parameters": {
        "jsCode": "// FLUX controller (t2i/i2i) — unified cache only\n// Outputs: action, chatId, reply_text, settings_text, reply_to_message_id,\n//          prompt, photo_file_id, ar, useRef, ref, refInterp, seed, quality, lang, balance\n\nconst sd = $getWorkflowStaticData('global');\nsd.flux        = sd.flux        || {};   // { [chatId]: { ar, ref, seed, quality, useRef } }\nsd.photoBucket = sd.photoBucket || {};   // { [chatId]: { files:[{file_id, message_id, ...}], savedAt } }\nsd.userLang    = sd.userLang    || {};\n\nconst msg    = $json.message || {};\nconst chatId = String($json.chatId || msg.chat?.id || $json.chat?.id || '');\nconst lang   = (typeof $json.lang === 'string' && /^(ru|en)$/i.test($json.lang))\n  ? $json.lang.toLowerCase()\n  : (sd.userLang[chatId] || (/^en/i.test(String(msg.from?.language_code||'')) ? 'en' : 'ru'));\nsd.userLang[chatId] = lang;\n\nconst balance = Number.isFinite(Number($json.balance)) ? Number($json.balance) : null;\nconst emit = (payload) => [{ json: { ...payload, lang, balance } }];\n\nconst promptNorm = typeof $json.prompt === 'string' ? $json.prompt : '';\nconst tgText     = typeof msg.text === 'string' ? msg.text : (typeof msg.caption === 'string' ? msg.caption : '');\nconst prompt     = (promptNorm || tgText || '').trim();\nconst hasText    = prompt.length > 0;\nconst reply_to_message_id = (msg.message_id ?? $json.deleteMessageId ?? null);\n\n// I18N\nconst I18N = {\n  ru: {\n    stored:   (n) => `📥 Фото сохранено (${n}). Отправьте текст — запущу i2i.`,\n    remind:   `Пришлите текст — запущу t2i, или фото затем текст — запущу i2i.`,\n    noop:     `Отправьте текст для запуска.`,\n    settings: (s) => [\n      `🧩 FLUX (auto t2i/i2i)`,\n      `📐 AR: ${s.ar} · ${s.dimText}`,\n      `🎛 Ref: ${s.useRef ? s.ref.toFixed(2) : 'off'} (interp: ${s.refInterp.toFixed(2)})`,\n      `🌱 Seed: ${s.seed === 0 ? 'random' : s.seed}`,\n      `⚙️ Quality: ${s.quality === 0 ? 'fast' : 'high'}`\n    ].join('\\n'),\n  },\n  en: {\n    stored:   (n) => `📥 Photo saved (${n}). Send text to run i2i.`,\n    remind:   `Send text to run t2i, or photo then text to run i2i.`,\n    noop:     `Send a text prompt to start.`,\n    settings: (s) => [\n      `🧩 FLUX (auto t2i/i2i)`,\n      `📐 AR: ${s.ar} · ${s.dimText}`,\n      `🎛 Ref: ${s.useRef ? s.ref.toFixed(2) : 'off'} (interp: ${s.refInterp.toFixed(2)})`,\n      `🌱 Seed: ${s.seed === 0 ? 'random' : s.seed}`,\n      `⚙️ Quality: ${s.quality === 0 ? 'fast' : 'high'}`\n    ].join('\\n'),\n  }\n};\nconst T = I18N[lang] || I18N.ru;\n\n// Flux settings\nconst defaults = { ar: '1:1', ref: 0.75, seed: 0, quality: 1, useRef: false };\nconst stored = (sd.flux[chatId] && typeof sd.flux[chatId] === 'object') ? sd.flux[chatId] : defaults;\n\nlet seedRaw = Number(stored.seed ?? 0);\nif (!Number.isFinite(seedRaw)) seedRaw = 0;\nconst seed = (seedRaw === 0) ? Math.floor(Math.random() * Number.MAX_SAFE_INTEGER) : seedRaw;\n\nlet ref = Number(stored.ref ?? defaults.ref);\nif (!Number.isFinite(ref)) ref = defaults.ref;\nref = Math.max(0, Math.min(1.2, ref));\nconst refInterp = (() => {\n  if (!Number.isFinite(ref) || ref <= 0) return 0;\n  const t = Math.min(Math.max(ref / 1.2, 0), 1);\n  return 5 + (1 - 5) * t;\n})();\n\nlet qRaw = Number(stored.quality ?? defaults.quality);\nconst quality = Number.isFinite(qRaw) ? qRaw : 1;\n\nfunction arToDims(ar) {\n  const [w, h] = String(ar || '1:1').split(':').map(n => parseInt(n,10) || 1);\n  const base = 1024;\n  const W = (w >= h) ? base : Math.round(base * w / h);\n  const H = (w >= h) ? Math.round(base * h / w) : base;\n  return `${W}×${H}`;\n}\nconst ar = (typeof stored.ar === 'string' && /^\\d+:\\d+$/.test(stored.ar)) ? stored.ar : defaults.ar;\nconst dimText = arToDims(ar);\n\n// detect incoming image\nlet incoming = null;\nif ($json.photo_file_id) {\n  incoming = {\n    file_id: $json.photo_file_id,\n    kind: $json.file_kind || 'photo',\n    file_name: $json.file_name || null,\n    mime_type: $json.mime_type || null,\n    message_id: (msg.message_id ?? $json.deleteMessageId ?? null),\n    savedAt: new Date().toISOString()\n  };\n}\nif (!incoming && msg.document?.file_id) {\n  const mt   = String(msg.document.mime_type || '').toLowerCase();\n  const name = String(msg.document.file_name || '');\n  const looksImage = mt.startsWith('image/') || /\\.(png|jpe?g|webp|bmp|gif|tiff?)$/i.test(name);\n  if (looksImage) {\n    incoming = {\n      file_id: msg.document.file_id,\n      kind: 'document',\n      file_name: name || null,\n      mime_type: msg.document.mime_type || null,\n      message_id: (msg.message_id ?? $json.deleteMessageId ?? null),\n      savedAt: new Date().toISOString()\n    };\n  }\n}\nif (!incoming && Array.isArray(msg.photo) && msg.photo.length) {\n  const best = msg.photo[msg.photo.length - 1];\n  incoming = {\n    file_id: best.file_id,\n    kind: 'photo',\n    file_name: null,\n    mime_type: null,\n    message_id: (msg.message_id ?? $json.deleteMessageId ?? null),\n    savedAt: new Date().toISOString()\n  };\n}\n\n// helpers\nfunction ensureBucket() {\n  return sd.photoBucket[chatId] ?? { files: [], savedAt: new Date().toISOString() };\n}\nfunction pushLimited(b, f, limit=4) {\n  const exists = (b.files || []).some(x => x.file_id === f.file_id);\n  if (!exists) {\n    (b.files = b.files || []).push(f);\n    if (b.files.length > limit) b.files = b.files.slice(-limit);\n  }\n  return b;\n}\n\n// guards\nif (!chatId) {\n  const settings_text = T.settings({ ar, dimText, useRef:false, ref:0, refInterp:0, seed, quality });\n  return emit({\n    action: 'REMIND', chatId: '',\n    reply_text: 'no chat', settings_text, reply_to_message_id: reply_to_message_id ?? null,\n    prompt:'', photo_file_id:null, ar, useRef:false, ref:0, refInterp:0, seed, quality\n  });\n}\n\n// /clear → очистка бакета\nif (/^\\/clear\\b/i.test(prompt)) {\n  if (sd.photoBucket[chatId]?.files) sd.photoBucket[chatId].files = [];\n  const settings_text = T.settings({ ar, dimText, useRef:false, ref, refInterp:0, seed, quality });\n  return emit({\n    action: 'CLEARED',\n    chatId,\n    reply_text: lang==='en' ? '🗑️ Reference bucket cleared.' : '🗑️ Копилка референсов очищена.',\n    settings_text,\n    reply_to_message_id,\n    prompt: '',\n    photo_file_id: null,\n    ar, useRef:false, ref:0, refInterp:0, seed, quality\n  });\n}\n\n// flow\nconst bucket = sd.photoBucket[chatId] || { files: [] };\nconst files  = bucket.files || [];\nconst hasStoredPhoto = files.length > 0;\n\n// 1) Фото без текста → складируем\nif (incoming && !hasText) {\n  const b = pushLimited(ensureBucket(), incoming, 4);\n  sd.photoBucket[chatId] = b;\n  const settings_text = T.settings({ ar, dimText, useRef:true, ref, refInterp, seed, quality });\n  return emit({\n    action: 'STORED',\n    chatId,\n    reply_text: T.stored(b.files.length),\n    settings_text,\n    reply_to_message_id,\n    prompt: '',\n    photo_file_id: null,\n    ar, useRef:true, ref, refInterp, seed, quality\n  });\n}\n\n// 2) t2i\nif (hasText && !hasStoredPhoto && !incoming) {\n  const settings_text = T.settings({ ar, dimText, useRef:false, ref:0, refInterp:0, seed, quality });\n  return emit({\n    action: 'GENERATE',\n    chatId,\n    reply_text: '',\n    settings_text,\n    reply_to_message_id,\n    prompt,\n    photo_file_id: null,\n    ar, useRef:false, ref:0, refInterp:0, seed, quality\n  });\n}\n\n// 3) i2i\nif (hasText && (hasStoredPhoto || incoming)) {\n  const all = hasStoredPhoto ? files.slice() : [];\n  if (incoming) all.push(incoming);\n  const primary = all[all.length - 1];\n\n  const settings_text = T.settings({ ar, dimText, useRef:true, ref, refInterp, seed, quality });\n  sd.photoBucket[chatId] = { files: all.slice(-4), savedAt: (bucket.savedAt || new Date().toISOString()) };\n\n  return emit({\n    action: 'GENERATE',\n    chatId,\n    reply_text: '',\n    settings_text,\n    reply_to_message_id,\n    prompt,\n    photo_file_id: primary?.file_id || null,\n    ar, useRef:true, ref, refInterp, seed, quality\n  });\n}\n\n// 4) подсказки/фолбек\nif (!hasText && !incoming && !hasStoredPhoto) {\n  const settings_text = T.settings({ ar, dimText, useRef:false, ref:0, refInterp:0, seed, quality });\n  return emit({\n    action: 'REMIND',\n    chatId,\n    reply_text: T.remind,\n    settings_text,\n    reply_to_message_id,\n    prompt: '',\n    photo_file_id: null,\n    ar, useRef:false, ref:0, refInterp:0, seed, quality\n  });\n}\n\nconst settings_text = T.settings({ ar, dimText, useRef:true, ref, refInterp, seed, quality });\nreturn emit({\n  action: 'NOOP',\n  chatId,\n  reply_text: T.noop,\n  settings_text,\n  reply_to_message_id,\n  prompt: '',\n  photo_file_id: null,\n  ar, useRef:true, ref, refInterp, seed, quality\n});\n"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1664,
        2272
      ],
      "id": "3e318ac2-354b-40dd-a744-cc1318b54142",
      "name": "Flux settings"
    },
    {
      "parameters": {
        "jsCode": "// n8n Code node — Clear caches after generation (UNIFIED FORMAT)\n// Берёт chatId из ответа Telegram sendPhoto / sendDocument:\n//   { ok: true, result: { chat: { id: ... }, ... } }\n//\n// ЧТО ДЕЛАЕТ:\n//   - sd.photoBucket[chatId]     (CLEARED)\n//   - sd.replyIndex[chatId]      (CLEARED)\n//   - sd.photoIndex[chatId]      (PRUNE по message_id; сама map остаётся)\n// $json.deleted_message_ids — по-прежнему опционален.\n\nconst sd = $getWorkflowStaticData('global');\n\n// Ensure unified caches exist\nsd.photoBucket = sd.photoBucket || {};     // { [chatId]: { files:[{file_id, message_id, ...}], savedAt } }\nsd.photoIndex  = sd.photoIndex  || {};     // { [chatId]: { [messageId]: file_id } }\nsd.replyIndex  = sd.replyIndex  || {};     // { [chatId]: number[] }\nsd.poll        = sd.poll        || {};     // left intact\n\n// ---- Определяем chatId из входящего item (ответ Telegram)\nconst item = $input.first()?.json || $json || {};\n\nconst chatId = $('Switch2').first().json.chatId;\n\n// Если вдруг чата нет — ничего не трогаем, но ok=true, чтобы пайплайн не падал\nif (!chatId) {\n  return [{\n    json: {\n      ok: true,\n      chatId: null,\n      skipped: 'no_chatId_in_input'\n    }\n  }];\n}\n\n// BEFORE snapshot\nconst before = {\n  photoBucket_files: sd.photoBucket?.[chatId]?.files?.length || 0,\n  photoIndex_keys:   sd.photoIndex?.[chatId] ? Object.keys(sd.photoIndex[chatId]).length : 0,\n  replyIndex_len:    Array.isArray(sd.replyIndex?.[chatId]) ? sd.replyIndex[chatId].length : 0,\n};\n\n// Собираем message_id для зачистки в photoIndex:\n// 1) из текущего photoBucket[chatId].files\n// 2) опционально — из $json.deleted_message_ids (если кто-то сверху передал)\nconst toPruneSet = new Set();\n\n// from bucket\nconst bucketFiles = sd.photoBucket?.[chatId]?.files || [];\nfor (const x of bucketFiles) {\n  const mid = Number(x?.message_id);\n  if (Number.isFinite(mid) && mid > 0) toPruneSet.add(String(mid));\n}\n\n// from payload (optional)\nconst extraIds = Array.isArray($json.deleted_message_ids) ? $json.deleted_message_ids : [];\nfor (const mid of extraIds) {\n  const n = Number(mid);\n  if (Number.isFinite(n) && n > 0) toPruneSet.add(String(n));\n}\n\n// Clear bucket + replyIndex для этого чата\ndelete sd.photoBucket[chatId];\ndelete sd.replyIndex[chatId];\n\n// Prune только нужные ключи из photoIndex (не дропаем карту целиком)\nlet prunedCount = 0;\nif (sd.photoIndex[chatId] && toPruneSet.size > 0) {\n  for (const key of toPruneSet) {\n    if (Object.prototype.hasOwnProperty.call(sd.photoIndex[chatId], key)) {\n      delete sd.photoIndex[chatId][key];\n      prunedCount++;\n    }\n  }\n  // Можно было бы удалить sd.photoIndex[chatId], если она опустела,\n  // но оставляем, чтобы не дёргать структуру.\n}\n\n// AFTER snapshot\nconst after = {\n  photoBucket_files: sd.photoBucket?.[chatId]?.files?.length || 0,\n  photoIndex_keys:   sd.photoIndex?.[chatId] ? Object.keys(sd.photoIndex[chatId]).length : 0,\n  replyIndex_len:    Array.isArray(sd.replyIndex?.[chatId]) ? sd.replyIndex[chatId].length : 0,\n};\n\nreturn [{\n  json: {\n    ok: true,\n    chatId,\n    pruned_from_photoIndex: prunedCount,\n    before,\n    after\n  }\n}];\n"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2656,
        2304
      ],
      "id": "07cb610e-d636-4d67-bbf6-6648a048af39",
      "name": "Clean_memory"
    },
    {
      "parameters": {
        "jsCode": "// n8n Code — FLUX: price + batch + aspect (w,h)\n\n// Ожидаем, что в $json уже есть:\n// - ar       — строка формата \"16:9\", \"3:4\" и т.п. (дефолт \"1:1\")\n// - quality  — 0 | 1 | 2  (дефолт 1)\n// - batch / batch_count — на будущее, сейчас из вебаппа не приходит (дефолт 1)\n\nconst arRaw = $json.ar ?? '1:1';\nlet quality = Number($json.quality);\nif (!Number.isFinite(quality) || quality < 0 || quality > 2) {\n  quality = 1; // дефолт Standard\n}\n\n// --- Таблица по качеству ---\n// Можно интерпретировать так: \"стоимость одной картинки в кредитах\"\nconst QUALITY_TABLE = {\n  0: { label: 'light',   credits: 1.5 },\n  1: { label: 'standard',credits: 2.0 },\n  2: { label: 'pro',     credits: 3.0 },\n};\n\n// --- Таблица по батчу (множитель) ---\n// batch = 1 → множитель 1\n// batch = 2 → 1.75\n// batch = 4 → 3\nlet batchCount = Number($json.batch_count ?? $json.batch ?? 1);\nif (!Number.isFinite(batchCount) || ![1, 2, 4].includes(batchCount)) {\n  batchCount = 1;\n}\n\nconst BATCH_TABLE = {\n  1: { multiplier: 1.0 },\n  2: { multiplier: 1.75 },\n  4: { multiplier: 3.0 },\n};\n\n// Берём конфиги (с фолбэком на дефолты)\nconst qCfg = QUALITY_TABLE[quality] ?? QUALITY_TABLE[1];\nconst bCfg = BATCH_TABLE[batchCount] ?? BATCH_TABLE[1];\n\n// Итоговая цена в кредитах\nconst creditsCost = qCfg.credits * bCfg.multiplier;\n\n// --- aspect → w,h ---\n// arRaw, например, \"16:9\"\nconst parts = String(arRaw).split(':');\nconst w = parseInt(parts[0], 10) || 1;\nconst h = parseInt(parts[1], 10) || 1;\n\n// Собираем расширенный json\nreturn [{\n  json: {\n    ...$json,\n    quality,\n    batch_count: batchCount,\n    price: creditsCost,\n    w,\n    h,\n    quality,\n    batch_count: batchCount,\n  }\n}];\n"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1840,
        2272
      ],
      "id": "6377556e-b9a2-41a8-b92e-2add18236be4",
      "name": "flux_price_and_aspect"
    },
    {
      "parameters": {
        "workflowId": {
          "__rl": true,
          "value": "leS7vcfjDNDlHg0o",
          "mode": "list",
          "cachedResultUrl": "/workflow/leS7vcfjDNDlHg0o",
          "cachedResultName": "Geni_AI_FLUX_v002"
        },
        "workflowInputs": {
          "mappingMode": "defineBelow",
          "value": {},
          "matchingColumns": [],
          "schema": [],
          "attemptToConvertTypes": false,
          "convertFieldsToString": true
        },
        "options": {
          "waitForSubWorkflow": true
        }
      },
      "type": "n8n-nodes-base.executeWorkflow",
      "typeVersion": 1.2,
      "position": [
        2304,
        2304
      ],
      "name": "Call FLUX",
      "id": "1919d1ec-14fd-4573-ad60-9a233c235d90"
    },
    {
      "parameters": {
        "jsCode": "// Seedream photo-bucket controller (унификация с Seedance)\n// Авто-режим: если есть фото → i2i; иначе → t2i.\n// Настройки читаем из sd.seedream[chatId] (quality 1k/2k/4k, ratio для t2i).\n\nconst sd = $getWorkflowStaticData('global');\nsd.photoBucket = sd.photoBucket || {};   // { [chatId]: { files:[{file_id,...}], savedAt } }\nsd.seedream    = sd.seedream    || {};   // { [chatId]: { quality, ratio, dims_t2i, long_edge_i2i, savedAt } }\n\n// ---------- inputs (normalized + telegram) ----------\nconst msg    = $json.message || {};\nconst chatId = String($json.chatId || msg.chat?.id || $json.chat?.id || '');\nconst tool   = $json.tool || 'seedream';\n\nconst normalizedPrompt = typeof $json.prompt === 'string' ? $json.prompt : '';\nconst tgText = typeof msg.text === 'string' ? msg.text : (typeof msg.caption === 'string' ? msg.caption : '');\nconst prompt = (normalizedPrompt || tgText || '').trim();\nconst hasText = prompt.length > 0;\n\n// reply target id (для reply_to_message_id)\nconst reply_to_message_id = (msg.message_id ?? $json.deleteMessageId ?? null);\n\n// ---------- Настройки Seedream ----------\n// Если не сохранены мини-аппом — подставим дефолты и посчитаем размеры\nfunction buildDefaultSeedream() {\n  // база 2K (из доки), и масштабируем на 1k/4k\n  const BASE_2K = {\n    \"1:1\":  { w: 2048, h: 2048 },\n    \"4:3\":  { w: 2304, h: 1728 },\n    \"3:4\":  { w: 1728, h: 2304 },\n    \"16:9\": { w: 2560, h: 1440 },\n    \"9:16\": { w: 1440, h: 2560 },\n    \"3:2\":  { w: 2496, h: 1664 },\n    \"2:3\":  { w: 1664, h: 2496 },\n    \"21:9\": { w: 3024, h: 1296 },\n  };\n  const SCALES = { '1k':0.5, '2k':1.0, '4k':2.0 };\n  const quality = '2k';\n  const ratio   = '1:1';\n  const dims    = BASE_2K[ratio];\n  return {\n    quality,\n    ratio,\n    dims_t2i: { w: dims.w, h: dims.h },\n    long_edge_i2i: 2048, // для i2i\n    savedAt: new Date().toISOString()\n  };\n}\n\nconst cfg0 = sd.seedream[chatId] || buildDefaultSeedream();\n// валидируем минимально\nconst quality = ['1k','2k','4k'].includes(String(cfg0.quality)) ? cfg0.quality : '2k';\nconst ratio   = [\"1:1\",\"4:3\",\"3:4\",\"16:9\",\"9:16\",\"3:2\",\"2:3\",\"21:9\"].includes(String(cfg0.ratio)) ? cfg0.ratio : '1:1';\nconst dims_t2i = (cfg0.dims_t2i && Number(cfg0.dims_t2i.w) && Number(cfg0.dims_t2i.h)) ? cfg0.dims_t2i : null;\nconst long_edge_i2i = Number(cfg0.long_edge_i2i) || (quality==='1k'?1024:(quality==='4k'?4096:2048));\n\nconst cfg = { quality, ratio, dims_t2i, long_edge_i2i, savedAt: cfg0.savedAt };\n\n// формат настройки для текста\nfunction fmtSettingsSeedream(c) {\n  const q = c.quality.toUpperCase();\n  const ar = c.ratio;\n  const dims = c.dims_t2i ? `${c.dims_t2i.w}×${c.dims_t2i.h}` : '—';\n  return [\n    `🧩 Mode: auto (t2i/i2i)`,\n    `🖼 Quality: ${q}`,\n    `📐 Aspect (t2i): ${ar} · ${dims}`,\n    `↔️ Long edge (i2i): ${c.long_edge_i2i}`\n  ].join('\\n');\n}\n\n// ---------- detect incoming image ----------\nlet incoming = null;\n\n// 1) нормализованный photo_file_id\nif ($json.photo_file_id) {\n  incoming = {\n    file_id: $json.photo_file_id,\n    kind: $json.file_kind || 'photo',\n    file_name: $json.file_name || null,\n    mime_type: $json.mime_type || null,\n    message_id: (msg.message_id ?? $json.deleteMessageId ?? null),\n    savedAt: new Date().toISOString()\n  };\n}\n\n// 2) Telegram document\nif (!incoming && msg.document?.file_id) {\n  const mt   = String(msg.document.mime_type || '').toLowerCase();\n  const name = String(msg.document.file_name || '');\n  const looksImage = mt.startsWith('image/') || /\\.(png|jpe?g|webp|bmp|gif|tiff?)$/i.test(name);\n  if (looksImage) {\n    incoming = {\n      file_id: msg.document.file_id,\n      kind: 'document',\n      file_name: name || null,\n      mime_type: msg.document.mime_type || null,\n      message_id: (msg.message_id ?? $json.deleteMessageId ?? null),\n      savedAt: new Date().toISOString()\n    };\n  }\n}\n\n// 3) Telegram photo[]\nif (!incoming && Array.isArray(msg.photo) && msg.photo.length) {\n  const best = msg.photo[msg.photo.length - 1];\n  incoming = {\n    file_id: best.file_id,\n    kind: 'photo',\n    file_name: null,\n    mime_type: null,\n    message_id: (msg.message_id ?? $json.deleteMessageId ?? null),\n    savedAt: new Date().toISOString()\n  };\n}\n\n// ---------- guards & commands ----------\nif (!chatId) {\n  return [{ json: { action: 'ERROR', reason: 'no_chat', got: Object.keys($json), reply_to_message_id } }];\n}\n\n// /clear очистка корзины\nif (/^\\/clear\\b/i.test(prompt)) {\n  delete sd.photoBucket[chatId];\n  return [{ json: { action:'CLEARED', chatId, reply:'🗑️ Копилка фото очищена.', settings_seedream: cfg, settings_text: fmtSettingsSeedream(cfg), reply_to_message_id } }];\n}\n\n// ---------- helpers ----------\nfunction ensureBucket() { return sd.photoBucket[chatId] ?? { files: [], savedAt: new Date().toISOString() }; }\nfunction pushLimited(b, f, limit=10) {\n  const exists = (b.files || []).some(x => x.file_id === f.file_id);\n  if (!exists) {\n    (b.files = b.files || []).push(f);\n    if (b.files.length > limit) b.files = b.files.slice(-limit);\n  }\n  return b;\n}\nfunction kbClearBucket() {\n  return [[ { text: '🗑 Очистить', callback_data: 'seedream:clear' } ]];\n}\n\n// ====================================================\n// ===================== FLOW =========================\n// ====================================================\n\n// 1) Фото БЕЗ текста → STORED (i2i будет возможен после текста)\nif (incoming && !hasText) {\n  const bucket = pushLimited(ensureBucket(), incoming, 10);\n  sd.photoBucket[chatId] = bucket;\n  return [{\n    json: {\n      action: 'STORED',\n      chatId,\n      tool,\n      storedCount: bucket.files.length,\n      reply: `📥 Фото сохранено (${bucket.files.length}). Теперь отправьте текст — запущу i2i.\\n\\n${fmtSettingsSeedream(cfg)}`,\n      inline_keyboard: kbClearBucket(),\n      settings_seedream: cfg,\n      settings_text: fmtSettingsSeedream(cfg),\n      reply_to_message_id\n    }\n  }];\n}\n\n// 2) Решение по запуску\nconst bucket = sd.photoBucket[chatId] || { files: [] };\nconst files  = bucket.files || [];\nconst hasStoredPhoto = files.length > 0;\n\n// Если есть текст и НЕТ фото → t2i\nif (hasText && !hasStoredPhoto && !incoming) {\n  // ожидается генерация t2i: из настроек отдаём dims_t2i\n  return [{\n    json: {\n      action: 'GENERATE',\n      mode: 't2i',\n      chatId,\n      tool,\n      prompt,\n      files: [],\n      wants_base64: false,\n      clearAfter: false,\n      next: 'BUILD_T2I_REQUEST',\n      // t2i параметры из настроек (если null — посчитаешь дальше)\n      t2i_dims: cfg.dims_t2i || null,  // {w,h} или null\n      t2i_ratio: cfg.ratio,            // '1:1' ...\n      t2i_quality: cfg.quality,        // '1k'|'2k'|'4k'\n      settings_seedream: cfg,\n      settings_text: fmtSettingsSeedream(cfg),\n      reply_to_message_id\n    }\n  }];\n}\n\n// /clear очистка корзины\nif (/^\\/clear\\b/i.test(prompt)) {\n  delete sd.photoBucket[chatId];\n  return [{\n    json: {\n      action: 'CLEARED',\n      chatId,\n      reply: '🗑️ Копилка фото очищена.',\n      settings_seedream: cfg,\n      settings_text: fmtSettingsSeedream(cfg),\n      reply_to_message_id\n    }\n  }];\n}\n\n// Если есть текст и ЕСТЬ фото (или пришло вместе) → i2i\nif (hasText && (hasStoredPhoto || incoming)) {\n  const all = hasStoredPhoto ? files.slice() : [];\n  if (incoming) all.push(incoming);\n  return [{\n    json: {\n      action: 'GENERATE',\n      mode: 'i2i',\n      chatId,\n      tool,\n      prompt,\n      files: all,               // массив {file_id,...}\n      primary_file: all[all.length-1] || null,\n      wants_base64: true,\n      clearAfter: true,         // после старта очищаем корзину\n      next: 'FETCH_FILE_BASE64',\n      // i2i целевая \"длинная сторона\"\n      i2i_long_edge: cfg.long_edge_i2i,\n      settings_seedream: cfg,\n      settings_text: fmtSettingsSeedream(cfg),\n      reply_to_message_id\n    }\n  }];\n}\n\n// Если текста нет и фото нет → подсказка\nif (!hasText && !incoming && !hasStoredPhoto) {\n  return [{\n    json: {\n      action: 'REMIND',\n      chatId,\n      tool,\n      reply: `Пришлите текст — запущу t2i, или пришлите фото, затем текст — запущу i2i.\\n\\n${fmtSettingsSeedream(cfg)}`,\n      settings_seedream: cfg,\n      settings_text: fmtSettingsSeedream(cfg),\n      reply_to_message_id\n    }\n  }];\n}\n\n// Фолбек (например, пришло фото и ещё фото без текста)\nreturn [{\n  json: {\n    action: 'NOOP',\n    chatId,\n    tool,\n    reply: `Отправьте текст для запуска.\\n\\n${fmtSettingsSeedream(cfg)}`,\n    settings_seedream: cfg,\n    settings_text: fmtSettingsSeedream(cfg),\n    reply_to_message_id\n  }\n}];\n"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1664,
        3072
      ],
      "id": "ef8f52dd-7dba-451a-a8e3-12cff8aa7384",
      "name": "Seedream — photo store & launch"
    },
    {
      "parameters": {
        "jsCode": "// Seedream — builder for Comet /v1/images/generations (t2i: WIDTHxHEIGHT, i2i: 1k/2k/4k)\n\nconst MODEL = 'bytedance-seedream-4-0-250828';\n\n// 1) Входы\nconst cfg    = $json.settings_seedream || {};\nconst prompt = String($json.prompt || '').trim();\nconst n      = Number.isFinite(Number($json.n)) ? Number($json.n) : 1;\n\n// helpers\nfunction isDataUrl(u){ return typeof u === 'string' && /^data:image\\/[a-z0-9.+-]+;base64,/i.test(u); }\nfunction isHttp(u){ return typeof u === 'string' && /^https?:\\/\\//i.test(u); }\nfunction extractImages(src){\n  const out = [];\n  if (!src) return out;\n  if (Array.isArray(src)) {\n    for (const it of src) {\n      if (typeof it === 'string' && (isDataUrl(it) || isHttp(it))) out.push(it);\n      else if (it && typeof it === 'object') {\n        const cand = it.dataUrl || it.url || it.data || it.image_url || it.imageUrl;\n        if (typeof cand === 'string' && (isDataUrl(cand) || isHttp(cand))) out.push(cand);\n      }\n    }\n  } else if (typeof src === 'string' && (isDataUrl(src) || isHttp(src))) {\n    out.push(src);\n  } else if (src && typeof src === 'object') {\n    const cand = src.dataUrl || src.url || src.data || src.image_url || src.imageUrl;\n    if (typeof cand === 'string' && (isDataUrl(cand) || isHttp(cand))) out.push(cand);\n  }\n  return out;\n}\n\n// 2) Соберём изображения (если есть — это i2i)\nlet images = [];\nimages = images.concat(extractImages($json.dataUrls));\nimages = images.concat(extractImages($json.images));\nimages = images.concat(extractImages($json.dataUrl));\nimages = images.concat(extractImages($json.image_url));\nimages = Array.from(new Set(images));\n\nconst isI2I = images.length > 0;\n\n// 3) Определяем size\nlet sizeValue;\n\n// t2i → WIDTHxHEIGHT\nif (!isI2I) {\n  const dims = $json.t2i_dims || cfg.dims_t2i || null;\n  let w = Number(dims?.w), h = Number(dims?.h);\n  if (!(Number.isFinite(w) && Number.isFinite(h) && w>0 && h>0)) {\n    // fallback по качеству/соотношению, если вдруг dims не пришли\n    // по умолчанию 2k квадрат\n    const q = (cfg.quality || '2k').toLowerCase();\n    if (q === '1k') { w = 1024; h = 1024; }\n    else if (q === '4k') { w = 4096; h = 4096; }\n    else { w = 2048; h = 2048; }\n  }\n  sizeValue = `${Math.round(w)}x${Math.round(h)}`;\n} else {\n  // i2i → '1k'|'2k'|'4k' (как раньше)\n  let size = (cfg.quality || '2k').toLowerCase();\n  if (!['1k','2k','4k'].includes(size)) size = '2k';\n  sizeValue = size;\n}\n\n// 4) Сборка payload\nconst body = {\n  model: MODEL,\n  prompt: prompt,\n  n: n,\n  size: sizeValue,\n  response_format: 'url',\n  watermark: false\n};\nif (isI2I) body.image = images;\n\n// 5) Выход\nreturn [{\n  json: {\n    method: 'POST',\n    url: 'https://api.cometapi.com/v1/images/generations',\n    headers: { 'Content-Type': 'application/json' },\n    body\n  }\n}];\n"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2384,
        3440
      ],
      "id": "3ef7d833-7c59-4a20-b67b-8461aa027bf7",
      "name": "Нормализовать task_id1",
      "alwaysOutputData": false
    },
    {
      "parameters": {
        "rules": {
          "values": [
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict",
                  "version": 2
                },
                "conditions": [
                  {
                    "id": "6337ea59-4635-406e-89d4-62505dae4f35",
                    "leftValue": "={{ $json.action }}",
                    "rightValue": "STORED",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    }
                  }
                ],
                "combinator": "and"
              }
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict",
                  "version": 2
                },
                "conditions": [
                  {
                    "id": "d6fc6a81-7010-4119-9d91-c66d79e68ac8",
                    "leftValue": "={{ $json.action }}",
                    "rightValue": "=GENERATE",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    }
                  }
                ],
                "combinator": "and"
              }
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict",
                  "version": 2
                },
                "conditions": [
                  {
                    "id": "9cff2c53-bb3e-49bf-9cfe-53963eac8978",
                    "leftValue": "={{ $json.action }}",
                    "rightValue": "CLEARED",
                    "operator": {
                      "type": "string",
                      "operation": "equals",
                      "name": "filter.operator.equals"
                    }
                  }
                ],
                "combinator": "and"
              }
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict",
                  "version": 2
                },
                "conditions": [
                  {
                    "id": "39ccebaf-cc95-4d0b-954f-71fab6178691",
                    "leftValue": "={{ $json.action }}",
                    "rightValue": "NOOP ",
                    "operator": {
                      "type": "string",
                      "operation": "equals",
                      "name": "filter.operator.equals"
                    }
                  }
                ],
                "combinator": "and"
              }
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict",
                  "version": 2
                },
                "conditions": [
                  {
                    "id": "a9f7ce9b-cd18-4138-83f1-8cb70c4643ed",
                    "leftValue": "={{ $json.action }}",
                    "rightValue": "REMIND",
                    "operator": {
                      "type": "string",
                      "operation": "equals",
                      "name": "filter.operator.equals"
                    }
                  }
                ],
                "combinator": "and"
              }
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.switch",
      "typeVersion": 3.2,
      "position": [
        2032,
        3024
      ],
      "id": "8f1d2ee7-ebaa-44be-996b-958adf1fbb1f",
      "name": "Switch"
    },
    {
      "parameters": {
        "jsCode": "const sd = $getWorkflowStaticData('global');\nsd.photoIndex  = sd.photoIndex  || {}; // { [chatId]: { [messageId]: file_id } }\n\nconst chatId = String($json.chatId || '');\nconst msgId  = Number($json.photo_message_id ?? $json.deleteMessageId ?? 0);\nconst fileId = $json.file_id || $json.photo_file_id || $json.primary_file?.file_id || null;\n\n\nif (chatId && msgId && fileId) {\n  sd.photoIndex[chatId] = sd.photoIndex[chatId] || {};\n  sd.photoIndex[chatId][msgId] = fileId;\n}\n\nreturn [{ json: $json }];\n"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2336,
        2976
      ],
      "id": "4792537f-3ed4-4206-8839-bab0460b5cdf",
      "name": "Code"
    },
    {
      "parameters": {
        "jsCode": "const sd = $getWorkflowStaticData('global');\nsd.replyIndex = sd.replyIndex || {}; // { [chatId]: number[] }\n\nconst chatId = String($json.chatId || $json.result?.chat?.id || '');\nconst sentId =\n  Number($json.result?.message_id) ||\n  Number($json.result?.message?.message_id) ||\n  Number($json.message_id) || 0;\n\nif (chatId && sentId) {\n  sd.replyIndex[chatId] = sd.replyIndex[chatId] || [];\n  if (!sd.replyIndex[chatId].includes(sentId)) sd.replyIndex[chatId].push(sentId);\n}\n\nreturn [{ json: $json }];\n"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2672,
        2976
      ],
      "id": "c566fc05-b832-407f-91a0-e1b2570df49c",
      "name": "Code24"
    },
    {
      "parameters": {
        "chatId": "={{ $json.chatId }}",
        "text": "={{ $json.lang === 'en'\n  ? `${$json.reply}\\n⚡ Generation cost: ${$json.price} credits.\\nTap a button to delete.`\n  : `${$json.reply}\\n⚡Стоимость генерации: ${$json.price} кредитов.\\nНажмите кнопку, чтобы удалить.`\n}}",
        "replyMarkup": "inlineKeyboard",
        "inlineKeyboard": {
          "rows": [
            {
              "row": {
                "buttons": [
                  {
                    "text": "={{ $json.lang === 'en'\n  ? '🗑 Delete this photo'\n  : '🗑 Удалить это фото'\n}}",
                    "additionalFields": {
                      "callback_data": "del:${$json.photo_message_id}"
                    }
                  }
                ]
              }
            },
            {
              "row": {
                "buttons": [
                  {
                    "text": "={{ $json.lang === 'en'\n  ? '🧺 Clear all'\n  : '🧺 Очистить все'\n}}",
                    "additionalFields": {
                      "callback_data": "del_all"
                    }
                  }
                ]
              }
            }
          ]
        },
        "additionalFields": {
          "appendAttribution": false,
          "reply_to_message_id": "={{ $json.reply_to_message_id }}"
        }
      },
      "id": "8cc27531-b8e6-4985-a60b-0826c028b6f9",
      "name": "Telegram",
      "type": "n8n-nodes-base.telegram",
      "typeVersion": 1.2,
      "position": [
        2496,
        2976
      ],
      "webhookId": "6969c97f-2c91-44e1-ae46-426d11c99897",
      "credentials": {
        "telegramApi": {
          "id": "ur7jSUPdiAaPVhCf",
          "name": "Geni AI"
        }
      }
    }
  ],
  "connections": {
    "Switch1": {
      "main": [
        [
          {
            "node": "Switch_lang_act",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "lang_inline",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Instruct_mes",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Seedream — photo store & launch",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Telegram Trigger1": {
      "main": [
        [
          {
            "node": "Switch_message_type",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "reply_markup": {
      "main": [
        [
          {
            "node": "deleteMessageMenu",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "menu": {
      "main": [
        [
          {
            "node": "switch_start",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "build_sub_invoice": {
      "main": [
        [
          {
            "node": "createInvoiceLink",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "payment.precheckout.parse": {
      "main": [
        [
          {
            "node": "answerPreCheckoutQuery",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "createInvoiceLink": {
      "main": [
        [
          {
            "node": "send_mes_inline_buy_plan",
            "type": "main",
            "index": 0
          },
          {
            "node": "delMes1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "answerPreCheckoutQuery": {
      "main": [
        []
      ]
    },
    "payment.parse_success": {
      "main": [
        [
          {
            "node": "apply_payment",
            "type": "main",
            "index": 0
          },
          {
            "node": "payment_pick_prompt_to_delete",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "apply_payment": {
      "main": [
        [
          {
            "node": "send_mes_payment",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Switch_message_type": {
      "main": [
        [
          {
            "node": "payment.precheckout.parse",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "payment.parse_success",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "message_reply_inline",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Switch_web_app",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Switch_web_app": {
      "main": [
        [
          {
            "node": "parse web_app_data",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "menu",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "switch_reply_menu": {
      "main": [
        [
          {
            "node": "reply_markup",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Switch1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "parse web_app_data": {
      "main": [
        []
      ]
    },
    "lang_inline": {
      "main": [
        [
          {
            "node": "delMes",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "delMes_keyboardMsg1": {
      "main": [
        []
      ]
    },
    "Switch_lang_act": {
      "main": [
        [
          {
            "node": "id_and_balance",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "send_mes_plan",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "build_sub_invoice",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "build_credits_packs",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "message_reply_inline": {
      "main": [
        [
          {
            "node": "Switch_inline_act",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Switch_inline_act": {
      "main": [
        [
          {
            "node": "delMes_targetMsg",
            "type": "main",
            "index": 0
          },
          {
            "node": "delMes_keyboardMsg",
            "type": "main",
            "index": 0
          },
          {
            "node": "Delete ONE image from memory",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Build items-to-delete",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "SB_set_user_lang",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "build_credits_invoice",
            "type": "main",
            "index": 0
          },
          {
            "node": "delMes_keyboardMsg2",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "SB_set_user_lang": {
      "main": [
        [
          {
            "node": "delMes_keyboardMsg1",
            "type": "main",
            "index": 0
          },
          {
            "node": "send_mes_lang_save",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build items-to-delete": {
      "main": [
        [
          {
            "node": "delMes2",
            "type": "main",
            "index": 0
          },
          {
            "node": "Clear ALL buckets/indexes",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "send_mes_balance": {
      "main": [
        [
          {
            "node": "delMes1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "send_mes_plan": {
      "main": [
        [
          {
            "node": "delMes1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Clear ALL buckets/indexes": {
      "main": [
        [
          {
            "node": "send_mes_clear",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "build_credits_packs": {
      "main": [
        [
          {
            "node": "reply_markup1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "createInvoiceLink_credits": {
      "main": [
        [
          {
            "node": "Merge",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "reply_markup1": {
      "main": [
        [
          {
            "node": "delMes1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "build_credits_invoice": {
      "main": [
        [
          {
            "node": "createInvoiceLink_credits",
            "type": "main",
            "index": 0
          },
          {
            "node": "create_payment_yookassa_credits",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "send_mes_payment1": {
      "main": [
        [
          {
            "node": "payment_store_prompt",
            "type": "main",
            "index": 0
          },
          {
            "node": "store_payment_message",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "payment_pick_prompt_to_delete": {
      "main": [
        [
          {
            "node": "delMes3",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "welcome_once": {
      "main": [
        [
          {
            "node": "switch_welcome",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "switch_start": {
      "main": [
        [
          {
            "node": "ensure_user",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "switch_reply_menu",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "switch_welcome": {
      "main": [
        [
          {
            "node": "send_wel_cred",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "send_mes_lang_save": {
      "main": [
        [
          {
            "node": "lang_to_home_after_set",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "lang_to_home_after_set": {
      "main": [
        [
          {
            "node": "Switch_message_type",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "delMes": {
      "main": [
        []
      ]
    },
    "delMes4": {
      "main": [
        [
          {
            "node": "lang_to_home_after_set1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "lang_to_home_after_set1": {
      "main": [
        [
          {
            "node": "Switch_message_type",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Instruct_mes": {
      "main": [
        [
          {
            "node": "delMes4",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "switch_home_inline": {
      "main": [
        [
          {
            "node": "send_home_inline",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Merge": {
      "main": [
        [
          {
            "node": "send_mes_payment1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "create_payment_yookassa_credits": {
      "main": [
        [
          {
            "node": "Merge",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "id_and_balance": {
      "main": [
        [
          {
            "node": "send_mes_balance",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "ensure_user": {
      "main": [
        [
          {
            "node": "welcome_once",
            "type": "main",
            "index": 0
          },
          {
            "node": "send_start_mes",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Switch2": {
      "main": [
        [
          {
            "node": "Code15",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Call FLUX",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Telegram19",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Telegram18",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Telegram18",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Telegram20": {
      "main": [
        [
          {
            "node": "Code23",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Code15": {
      "main": [
        [
          {
            "node": "Telegram20",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Flux settings": {
      "main": [
        [
          {
            "node": "flux_price_and_aspect",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "flux_price_and_aspect": {
      "main": [
        [
          {
            "node": "Switch2",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Call FLUX": {
      "main": [
        [
          {
            "node": "Clean_memory",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Seedream — photo store & launch": {
      "main": [
        [
          {
            "node": "Switch",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Switch": {
      "main": [
        [
          {
            "node": "Code",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Code": {
      "main": [
        [
          {
            "node": "Telegram",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Telegram": {
      "main": [
        [
          {
            "node": "Code24",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {
    "executionOrder": "v1",
    "timezone": "Asia/Bangkok",
    "callerPolicy": "workflowsFromSameOwner",
    "availableInMCP": false,
    "errorWorkflow": "I7DgFold1DUWIFbw"
  },
  "staticData": {
    "global": {
      "menu": {
        "140652": {
          "path": "account",
          "updatedAt": "2025-12-07T19:05:09.927Z",
          "lang": "ru"
        },
        "748140117": {
          "path": "image/flux",
          "updatedAt": "2025-12-10T02:40:42.956Z",
          "lang": "en"
        },
        "1110290411": {
          "path": "",
          "updatedAt": "2025-12-09T14:25:21.452Z",
          "lang": "en"
        },
        "8159721308": {
          "path": "image/seedream",
          "updatedAt": "2025-11-09T06:52:54.244Z",
          "lang": "ru"
        },
        "7103210852": {
          "path": "",
          "updatedAt": "2025-12-03T11:02:01.883Z",
          "lang": "ru"
        },
        "7108317408": {
          "path": "account",
          "updatedAt": "2025-12-06T12:00:30.536Z",
          "lang": "ru"
        }
      },
      "session": {
        "140652": {
          "tool": "account"
        },
        "748140117": {
          "tool": "flux"
        },
        "1110290411": {
          "tool": null
        },
        "8159721308": {
          "tool": "seedream"
        },
        "7103210852": {
          "tool": null
        },
        "7108317408": {
          "tool": "account"
        }
      },
      "photoBucket": {
        "748140117": {
          "files": [
            {
              "file_id": "AgACAgIAAxkBAAIRiWk4fw_3W-V_Xp5XVrSEPtdRHu76AALbDWsbm_rJSeEcAgmY6FB7AQADAgADeQADNgQ",
              "kind": "photo",
              "file_name": null,
              "mime_type": null,
              "message_id": 4489,
              "savedAt": "2025-12-09T19:50:58.332Z"
            },
            {
              "file_id": "AgACAgIAAxkBAAIRi2k4f2KEws_bOImu98vR8cjPOUUTAALeDWsbm_rJSdfDFsK7ry86AQADAgADeQADNgQ",
              "kind": "photo",
              "file_name": null,
              "mime_type": null,
              "message_id": 4493,
              "savedAt": "2025-12-09T19:54:47.567Z"
            }
          ],
          "savedAt": "2025-12-09T19:50:58.332Z"
        }
      },
      "nanobanana": {},
      "photoIndex": {},
      "replyIndex": {
        "748140117": [
          4494
        ]
      },
      "photoBucket_seedance": {},
      "photoIndex_seedance": {},
      "replyIndex_seedance": {},
      "poll": {},
      "veo": {
        "748140117": {
          "model": "veo3.1",
          "ratio": "9:16",
          "mode": "first_last",
          "savedAt": "2025-11-04T19:03:47.865Z"
        }
      },
      "flux": {
        "748140117": {
          "ar": "3:4",
          "ref": 0,
          "seed": 1,
          "quality": 2,
          "batch": 1,
          "useRef": false,
          "savedAt": "2025-12-09T19:52:13.008Z"
        },
        "1110290411": {
          "ar": "1:1",
          "ref": 0,
          "seed": 2053943465563945,
          "quality": 1,
          "useRef": false,
          "savedAt": "2025-12-07T19:12:57.350Z"
        }
      },
      "seedance": {
        "748140117": {
          "mode": "i2v",
          "tier": "lite",
          "model": "bytedance-seedance-1-0-lite-i2v-250428",
          "i2v_mode": "first_last",
          "resolution": "720p",
          "ratio": "auto",
          "duration": 5,
          "camerafixed": false,
          "savedAt": "2025-11-04T18:33:27.501Z"
        }
      },
      "seedream": {
        "748140117": {
          "quality": "2k",
          "ratio": "3:2",
          "dims_t2i": {
            "w": 2496,
            "h": 1664
          },
          "long_edge_i2i": 2048,
          "savedAt": "2025-12-09T22:42:50.547Z"
        }
      },
      "sora": {
        "748140117": {
          "model": "sora-2",
          "ratio": "9:16",
          "duration": 4,
          "savedAt": "2025-10-18T05:21:22.904Z"
        }
      },
      "userLang": {
        "140652": "ru",
        "748140117": "en",
        "1110290411": "en",
        "8159721308": "ru",
        "7103210852": "ru",
        "7108317408": "ru"
      },
      "userSub": {},
      "photoBucket_flux": {
        "748140117": {
          "files": [
            {
              "file_id": "BQACAgIAAxkBAAILP2kVTaNKYG4mB-SIaQOCq74WabQxAAJOgwACTsuwSOtXrRWiOuEhNgQ",
              "kind": "document",
              "file_name": "file_1.jpg",
              "mime_type": "image/jpeg",
              "message_id": 2879,
              "savedAt": "2025-11-13T03:11:17.163Z"
            },
            {
              "file_id": "AgACAgIAAxkBAAILRmkVVIP7r4sEnqB8NrgSKHHErTlkAAIXC2sbTsuwSCCIYRNZzyf7AQADAgADeQADNgQ",
              "kind": "photo",
              "file_name": null,
              "mime_type": null,
              "message_id": 2886,
              "savedAt": "2025-11-13T03:40:36.836Z"
            }
          ],
          "savedAt": "2025-11-13T03:11:17.163Z"
        }
      },
      "payments": {
        "stxSeN0lqz1v6MVtol4O0B1ptYsFbhkGq9MEItzInBlxW6BO_k_fQK85Xoeiv4-SvDOBqeAPCaSuk7_DZRKyXcVehs5-8GRDbzdowYOc-7NYX1Dmnlb1wS7KT7Kp9RvABtR": true,
        "stxNEHIte1HGZLuft4lE3RN42bS-lTTnurc-TnKgq7-0SD-_86bEmO8b1wCSbCK6gNCKp9-Xgk4iqpT9T_knCgWWBzx7vREnapp-M_cR35yaAJl3uWr3RigtxwdZZWUvc5L": true,
        "stxpNKZQkVGFslWa364AkWWE_iUUGdJrn3D95cSScRdEYqic6goUPMdOnwjGNnp2tOCbNcSY_6B2mAOnuRBNT0yrshn2ViPJv7cVk08U2-cwLxjNDCw2sYJH9czIkkiWJ5j": true
      },
      "paymentPrompt": {
        "140652": 4017,
        "748140117": 4115
      }
    }
  },
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "pinData": {
    "Telegram Trigger1": [
      {
        "json": {
          "update_id": 460304190,
          "message": {
            "message_id": 4556,
            "from": {
              "id": 748140117,
              "is_bot": false,
              "first_name": "Grigoriy",
              "last_name": "Voyakin",
              "username": "GRIGORIY_VOYAKIN",
              "language_code": "ru"
            },
            "chat": {
              "id": 748140117,
              "first_name": "Grigoriy",
              "last_name": "Voyakin",
              "username": "GRIGORIY_VOYAKIN",
              "type": "private"
            },
            "date": 1765320535,
            "web_app_data": {
              "button_text": "⚙️ Settings",
              "data": "{\"type\":\"seedream_settings\",\"model\":\"4.0\",\"quality\":\"2k\",\"ratio\":\"3:2\",\"dims_t2i\":{\"w\":2496,\"h\":1664},\"long_edge_i2i\":2048}"
            }
          }
        }
      }
    ]
  },
  "versionId": "9ff68b38-0758-4e45-a6e0-011208f9f4c4",
  "activeVersionId": "9ff68b38-0758-4e45-a6e0-011208f9f4c4",
  "versionCounter": 935,
  "triggerCount": 1,
  "shared": [
    {
      "updatedAt": "2025-12-09T17:22:27.440Z",
      "createdAt": "2025-12-09T17:22:27.440Z",
      "role": "workflow:owner",
      "workflowId": "HfvL0uSdAtwjBN7u",
      "projectId": "MHclKTSzdRCLxmxU",
      "project": {
        "updatedAt": "2025-05-06T12:49:51.317Z",
        "createdAt": "2025-05-06T12:48:38.577Z",
        "id": "MHclKTSzdRCLxmxU",
        "name": "Grigoriy Voyakin <grigoriyvoyakinwork@gmail.com>",
        "type": "personal",
        "icon": null,
        "description": null,
        "projectRelations": [
          {
            "updatedAt": "2025-05-06T12:48:38.577Z",
            "createdAt": "2025-05-06T12:48:38.577Z",
            "userId": "15659665-1e18-4119-8f87-b50a3cb257b7",
            "projectId": "MHclKTSzdRCLxmxU",
            "user": {
              "updatedAt": "2025-12-10T02:57:46.000Z",
              "createdAt": "2025-05-06T12:48:38.340Z",
              "id": "15659665-1e18-4119-8f87-b50a3cb257b7",
              "email": "grigoriyvoyakinwork@gmail.com",
              "firstName": "Grigoriy",
              "lastName": "Voyakin",
              "personalizationAnswers": {
                "version": "v4",
                "personalization_survey_submitted_at": "2025-05-09T12:49:28.002Z",
                "personalization_survey_n8n_version": "1.91.3"
              },
              "settings": {
                "userActivated": true,
                "easyAIWorkflowOnboarded": true,
                "firstSuccessfulWorkflowId": "6ndVsjNc12yZ7vD6",
                "userActivatedAt": 1747397744116,
                "npsSurvey": {
                  "responded": true,
                  "lastShownAt": 1756144477136
                }
              },
              "disabled": false,
              "mfaEnabled": false,
              "lastActiveAt": "2025-12-09",
              "isPending": false
            }
          }
        ]
      }
    }
  ],
  "tags": [
    {
      "updatedAt": "2025-10-29T09:15:49.403Z",
      "createdAt": "2025-10-29T09:15:49.403Z",
      "id": "NFsjRzvK9b3zMBTF",
      "name": "docs"
    }
  ],
  "activeVersion": {
    "updatedAt": "2025-12-10T02:57:21.280Z",
    "createdAt": "2025-12-10T02:57:21.280Z",
    "versionId": "9ff68b38-0758-4e45-a6e0-011208f9f4c4",
    "workflowId": "HfvL0uSdAtwjBN7u",
    "nodes": [
      {
        "parameters": {
          "rules": {
            "values": [
              {
                "conditions": {
                  "options": {
                    "caseSensitive": true,
                    "leftValue": "",
                    "typeValidation": "strict",
                    "version": 2
                  },
                  "conditions": [
                    {
                      "id": "78542b6d-15c2-4809-935c-49d1e17dcc1e",
                      "leftValue": "={{ $json.tool }}",
                      "rightValue": "account",
                      "operator": {
                        "type": "string",
                        "operation": "equals",
                        "name": "filter.operator.equals"
                      }
                    }
                  ],
                  "combinator": "and"
                }
              },
              {
                "conditions": {
                  "options": {
                    "caseSensitive": true,
                    "leftValue": "",
                    "typeValidation": "strict",
                    "version": 2
                  },
                  "conditions": [
                    {
                      "id": "dfbae9da-3cc7-488e-9f71-f21a4b9b4345",
                      "leftValue": "={{ $json.tool }}",
                      "rightValue": "lang",
                      "operator": {
                        "type": "string",
                        "operation": "equals",
                        "name": "filter.operator.equals"
                      }
                    }
                  ],
                  "combinator": "and"
                }
              },
              {
                "conditions": {
                  "options": {
                    "caseSensitive": true,
                    "leftValue": "",
                    "typeValidation": "strict",
                    "version": 2
                  },
                  "conditions": [
                    {
                      "id": "19714412-73ff-4c46-ba9e-4f4276157e72",
                      "leftValue": "={{ $json.tool }}",
                      "rightValue": "instruction",
                      "operator": {
                        "type": "string",
                        "operation": "equals",
                        "name": "filter.operator.equals"
                      }
                    }
                  ],
                  "combinator": "and"
                }
              },
              {
                "conditions": {
                  "options": {
                    "caseSensitive": true,
                    "leftValue": "",
                    "typeValidation": "strict",
                    "version": 2
                  },
                  "conditions": [
                    {
                      "id": "d6fc6a81-7010-4119-9d91-c66d79e68ac8",
                      "leftValue": "={{ $json.tool }}",
                      "rightValue": "=flux",
                      "operator": {
                        "type": "string",
                        "operation": "equals"
                      }
                    }
                  ],
                  "combinator": "and"
                }
              }
            ]
          },
          "options": {}
        },
        "type": "n8n-nodes-base.switch",
        "typeVersion": 3.2,
        "position": [
          1328,
          2016
        ],
        "id": "f6103241-cbad-4932-a4e4-d845d2eb65c2",
        "name": "Switch1"
      },
      {
        "parameters": {
          "updates": [
            "message",
            "callback_query",
            "pre_checkout_query"
          ],
          "additionalFields": {}
        },
        "id": "04d5c88f-05c1-49e8-b34b-26ceb2ab22d2",
        "name": "Telegram Trigger1",
        "type": "n8n-nodes-base.telegramTrigger",
        "typeVersion": 1,
        "position": [
          -1568,
          1952
        ],
        "webhookId": "aaa5322f-da8f-4d75-b626-ae10bb379fbe",
        "credentials": {
          "telegramApi": {
            "id": "ur7jSUPdiAaPVhCf",
            "name": "Geni AI"
          }
        }
      },
      {
        "parameters": {
          "method": "POST",
          "url": "https://api.telegram.org/bot8495842221:AAHBriXvzudyazB2f8z3ilGQ8A5ESdjjtl8/sendMessage",
          "sendBody": true,
          "bodyParameters": {
            "parameters": [
              {
                "name": "chat_id",
                "value": "={{ $json.chatId }}"
              },
              {
                "name": "text",
                "value": "={{ $json.replyText }}"
              },
              {
                "name": "parse_mode",
                "value": "HTML"
              },
              {
                "name": "reply_markup",
                "value": "={{ $json.replyMarkup }}"
              },
              {
                "name": "disable_notification",
                "value": "={{ $json.silent }}"
              }
            ]
          },
          "options": {}
        },
        "type": "n8n-nodes-base.httpRequest",
        "typeVersion": 4.2,
        "position": [
          624,
          1792
        ],
        "id": "69d935ba-a076-4654-bc52-a14458e6180e",
        "name": "reply_markup",
        "retryOnFail": true
      },
      {
        "parameters": {
          "jsCode": "// === MENU (RU/EN localization) ===\n// Keep original logic; add Account section + \"flat\" actions inside Account\n\n// ---- state\nconst sd = $getWorkflowStaticData('global');\nsd.menu     = sd.menu     || {};\nsd.session  = sd.session  || {};\nsd.userLang = sd.userLang || {};\nsd.userSub  = sd.userSub  || {}; // { [chatId]: { active: boolean } } optional cache\n\n// ---- input\nconst msg    = $json.message || {};\nconst chatId = String(msg.chat?.id || $json.chat?.id || '');\nconst msgId  = msg.message_id;\nconst textIn = String(msg.text ?? msg.caption ?? '').trim();\n\n// ---- language resolve: 1) payload.lang 2) cached 3) Telegram UI hint 4) ru\nlet lang = (typeof $json.lang === 'string' && /^(ru|en)$/i.test($json.lang)) ? $json.lang.toLowerCase() : null;\nif (!lang) lang = sd.userLang[chatId] || null;\nif (!lang) lang = (/^en/i.test(String(msg.from?.language_code || ''))) ? 'en' : 'ru';\nsd.userLang[chatId] = lang;\n\n// ---- localization dict\nconst I18N = {\n  ru: {\n    home: '🏠 Главная',\n    back: '⬅️ Назад',\n    account: '👤 Аккаунт',\n    mainTitle: '🏠 Главная страница',\n    mainDesc: 'Выберите инструмент',\n    help: '❔ Помощь',\n    helpDesc: 'Раздел помощи',\n    helpInstruction: '📘 Инструкция',\n    helpInstructionDesc: 'Как пользоваться ботом',\n    language: '🌐 Язык',\n    languageDesc: 'Выбор языка интерфейса',\n    image: '🖼 Image',\n    imageDesc: 'Генерация изображений',\n    gpt: '💬 GPT',\n    gptDesc: 'Чат с LLM',\n    audio: '🎵 Audio',\n    audioDesc: 'Аудио-инструменты',\n    video: '🎬 Video',\n    videoDesc: 'Видео-инструменты',\n    seedream: '🌊 Seedream',\n    nanobanana: '🍌 Nano Banana',\n    flux: '⚡️ FLUX',\n    fluxDesc: 'Генерация изображения с FLUX',\n    seedance: '🎞 Seedance',\n    veo: '⭕ Veo',\n    sora: '🌙 Sora 2',\n    settings: '⚙️ Settings',\n    placeholder: 'Меню',\n\n    // Account\n    accTitle: '👤 Аккаунт',\n    accDesc: 'Управление аккаунтом',\n    accBalance: '💰 Баланс',\n    accStatus: '📦 Статус',\n    accBuySub: '💳 Купить подписку',\n    accBuyCreds: '⚡️ Купить кредиты',\n\n    // NEW: Community\n    community: '👥 Сообщество',\n    communityDesc: 'Ссылка на канал @voyakin_geni',\n  },\n  en: {\n    home: '🏠 Home',\n    back: '⬅️ Back',\n    account: '👤 Account',\n    mainTitle: '🏠 Home',\n    mainDesc: 'Pick a tool',\n    help: '❔ Help',\n    helpDesc: 'Help section',\n    helpInstruction: '📘 Instructions',\n    helpInstructionDesc: 'How to use the bot',\n    language: '🌐 Language',\n    languageDesc: 'Choose interface language',\n    image: '🖼 Image',\n    imageDesc: 'Image generation',\n    gpt: '💬 GPT',\n    gptDesc: 'Chat with LLM',\n    audio: '🎵 Audio',\n    audioDesc: 'Audio tools',\n    video: '🎬 Video',\n    videoDesc: 'Video tools',\n    seedream: '🌊 Seedream',\n    nanobanana: '🍌 Nano Banana',\n    flux: '⚡️ FLUX',\n    fluxDesc: 'Generating an image with FLUX',\n    seedance: '🎞 Seedance',\n    veo: '⭕ Veo',\n    sora: '🌙 Sora 2',\n    settings: '⚙️ Settings',\n    placeholder: 'Menu',\n\n    // Account\n    accTitle: '👤 Account',\n    accDesc: 'Manage your account',\n    accBalance: '💰 Balance',\n    accStatus: '📦 Status',\n    accBuySub: '💳 Buy subscription',\n    accBuyCreds: '⚡️ Buy credits',\n\n    // NEW: Community\n    community: '👥 Community',\n    communityDesc: 'Link to channel @voyakin_geni',\n  }\n};\nconst L = (k)=> (I18N[lang]||I18N.ru)[k] ?? k;\n\n// ---- webapps\nconst WEBAPP_ACCOUNT  = 'https://tgmenu.pages.dev/account';\nconst WEBAPP_SETTINGS = 'https://app-ru.geni-ai.online/settings';\n\n// ---- menu tree (localized)\nconst MENU = {\n  key: '', title: L('mainTitle'), desc: L('mainDesc'), children: [\n    { key: 'image',  title: L('image'),  desc: L('imageDesc'), children: [\n      // [V1 HIDE] Seedream / Nano Banana временно отключены\n      { key: 'seedream',    title: L('seedream'), desc: '—', command: 'seedream', children: [\n         { key: 'settings', title: L('settings'), type: 'webapp', webUrl: WEBAPP_SETTINGS + '/seedream.html?tool=seedream' },\n       ]},\n      // { key: 'nano banana', title: L('nanobanana'), desc: '—', command: 'nano banana', children: [\n      //   { key: 'settings', title: L('settings'), type: 'webapp', webUrl: WEBAPP_SETTINGS + '/nanobanana.html' },\n      // ]},\n      { key: 'flux',        title: L('flux'), desc: L('fluxDesc'), command: 'flux', children: [\n        { key: 'settings', title: L('settings'), type: 'webapp', webUrl: WEBAPP_SETTINGS + '/flux3.html?tool=flux' },\n      ]},\n    ]},\n\n    // [V1 HIDE] GPT / Audio / Video временно отключены\n    // { key: 'gpt',   title: L('gpt'),   desc: L('gptDesc'),   command: 'gpt' },\n    // { key: 'audio', title: L('audio'), desc: L('audioDesc'), command: 'audio' },\n    // { key: 'video', title: L('video'), desc: L('videoDesc'), command: 'video', children: [\n    //   { key: 'seedance', title: L('seedance'), desc: '—', command: 'seedance', children: [\n    //     { key: 'settings', title: L('settings'), type: 'webapp', webUrl: WEBAPP_SETTINGS + '/seedance.html' }\n    //   ]},\n    //   { key: 'veo', title: L('veo'), desc: '—', command: 'veo', children: [\n    //     { key: 'settings', title: L('settings'), type: 'webapp', webUrl: WEBAPP_SETTINGS + '/veo.html' }\n    //   ]},\n    //   { key: 'sora', title: L('sora'), desc: '—', command: 'sora', children: [\n    //     { key: 'settings', title: L('settings'), type: 'webapp', webUrl: WEBAPP_SETTINGS + '/sora_2.html' }\n    //   ]},\n    // ]},\n\n    { key: 'help',  title: L('help'),  desc: L('helpDesc'),  children: [\n      { key: 'language', title: L('language'), desc: L('languageDesc'), command: 'lang' },\n      { key: 'instruction', title: L('helpInstruction'), desc: L('helpInstructionDesc'), command: 'instruction' },\n    ]},\n\n    // ACCOUNT (top-level)\n    { key: 'account', title: L('accTitle'), desc: L('accDesc'), command: 'account', children: [\n      { key: 'balance',     title: L('accBalance'),  desc: '—' },\n     // { key: 'status',      title: L('accStatus'),   desc: '—' },\n      { key: 'buy_sub',     title: L('accBuySub'),   desc: '—' },   // UI-сильно скрыт ниже\n      { key: 'buy_credits', title: L('accBuyCreds'), desc: '—' },\n    ]},\n\n     { key: 'community', title: L('community'), desc: L('communityDesc'), command: 'community' },\n  ]\n};\n\n// ---- helpers\nconst BTN_MAIN = L('home');\nconst BTN_BACK = L('back');\nconst BTN_ACCOUNT = L('account'); // web_app button text at bottom (сейчас не используем)\n\nconst hasMedia =\n  (Array.isArray(msg.photo) && msg.photo.length>0) ||\n  !!msg.document;\n\nconst norm = s => (s||'').normalize('NFKC').toLowerCase()\n  .replace(/[\\p{Emoji_Presentation}\\p{Emoji}\\uFE0F]/gu,'')\n  .replace(/[\\u0300-\\u036f]/g,'')\n  .replace(/[^\\p{L}\\p{N}]+/gu,'').trim();\n\nconst split = p => p ? p.split('/').filter(Boolean) : [];\nfunction nodeByPath(path){\n  if(!path) return MENU;\n  let n=MENU;\n  for(const part of split(path)){\n    n=(n.children||[]).find(c=>c.key===part);\n    if(!n) return MENU;\n  }\n  return n;\n}\nfunction parentPath(path){ const a=split(path); a.pop(); return a.join('/'); }\nfunction childKeyByInput(path,txt){\n  const want=norm(txt); const n=nodeByPath(path);\n  const here=(n.children||[]).find(c=>[c.key,c.title].some(v=>norm(v)===want))?.key;\n  if (here) return here;\n  const root=(MENU.children||[]).find(c=>[c.key,c.title].some(v=>norm(v)===want))?.key;\n  return root||null;\n}\nfunction matchInAccount(txt){\n  const acc = (MENU.children||[]).find(c=>c.key==='account');\n  if (!acc) return null;\n  const want = norm(txt);\n  const hit = (acc.children||[]).find(c => [c.key,c.title].some(v => norm(v)===want));\n  return hit?.key || null; // 'balance'|'status'|'buy_sub'|'buy_credits'\n}\n\n// ---- nav/mode\nlet path = sd.menu[chatId]?.path || '';\nlet session = sd.session[chatId] || { tool:null };\nlet account_action = null; // <- FLAT actions inside account\n\nconst low = norm(textIn);\n\n// отдельный флаг именно для /start\nconst isStartCmd = /^\\/start\\b/i.test(textIn);\n\n// \"меню\" = /start, /menu и текстовые синонимы\nconst isMenuCmd =\n  isStartCmd ||\n  /^\\/menu\\b/i.test(textIn) ||\n  ['menu','main','home','меню','главная','домой'].includes(low);\n\nconst isBack    = [norm(BTN_BACK),'back','назад'].includes(low);\n\n// команды/шорткаты\nconst isBalanceCmd    = /^\\/balance\\b/i.test(textIn)     || low === 'balance'    || low === norm(L('accBalance'));\nconst isBuyCreditsCmd = /^\\/buy_credits\\b/i.test(textIn) || low === 'buycredits' || low === 'buy_credits';\nconst isLangCmd       = /^\\/lang\\b/i.test(textIn)        || low === 'lang';\n\n// navigation changes\nif (isMenuCmd) {\n  path=''; session.tool=null;\n} else if (isBack) {\n  const prev=parentPath(path);\n  if (split(path).length===1) session.tool=null;\n  path=prev;\n} else if (textIn) {\n  // direct \"Language\" jump: кнопка «Язык» ИЛИ /lang / lang\n  if (norm(textIn) === norm(L('language')) || isLangCmd) {\n    path='help/language';\n    session.tool='lang';\n  } else if (isBalanceCmd) {\n    // прямой вызов баланса: /balance или \"Баланс\"\n    path = 'account';\n    account_action = 'balance';\n    session.tool = 'account';\n  } else if (isBuyCreditsCmd) {\n    // прямой вызов покупки кредитов: /buy_credits\n    path = 'account';\n    account_action = 'buy_credits';\n    session.tool = 'account';\n  } else {\n    const k = childKeyByInput(path, textIn); // can be child of current or root\n    if (k){\n      const isRootChild = (MENU.children||[]).some(c=>c.key===k);\n\n      if (isRootChild) {\n        // go to root child as usual\n        path = k;\n        const chosen = nodeByPath(path);\n        if (chosen?.command) session.tool = chosen.command;\n        else session.tool = null;\n      } else {\n        // child of current node\n        if (path === 'account') {\n          // FLAT actions: keep path at 'account' and emit action\n          account_action = k;       // 'balance'|'status'|'buy_sub'|'buy_credits'\n          session.tool    = 'account';\n          // DO NOT change path\n        } else {\n          // normal deep navigation for other sections\n          path = path ? `${path}/${k}` : k;\n          const chosen = nodeByPath(path);\n          if (chosen?.command) session.tool = chosen.command;\n          else if (!chosen?.children?.length) session.tool = null;\n        }\n      }\n    }\n  }\n}\n\n// persist\nsd.menu[chatId]    = { path, updatedAt: new Date().toISOString(), lang };\nsd.session[chatId] = session;\n\nconst node = nodeByPath(path);\nconst cameFromNav = isMenuCmd || isBack || !!childKeyByInput(parentPath(path), textIn);\nconst isLangTool  = session.tool === 'lang';\nconst isAccountAction = session.tool === 'account' && !!account_action;\nconst isInstructionTool = session.tool === 'instruction';\n\n// shouldSendMenu: no menu when lang inline or account action (we'll send specific message)\nconst shouldSendMenu = (isLangTool || isAccountAction || isInstructionTool) ? false : cameFromNav;\nconst isPrompt = !!session.tool && !shouldSendMenu && (textIn || hasMedia);\n\n// ====== Home inline buttons (only on root + когда реально рисуем меню) ======\nlet homeInlineShow   = false;\nlet homeInlineText   = null;\nlet homeInlineMarkup = null;\n\nif (!path && shouldSendMenu) {\n  homeInlineShow = true;\n\n  // Текст под инлайн-кнопками\n  homeInlineText = (lang === 'ru')\n    ? '🚀 Быстрый старт'\n    : '🚀 Quick start';\n\n  // Пример кнопок — под себя подправишь\n  if (lang === 'ru') {\n    homeInlineMarkup = {\n      inline_keyboard: [\n        [\n          { text: '⚡️ FLUX',            callback_data: 'home:flux' },\n        ],\n        [\n          { text: '📘 Инструкция',      callback_data: 'home:instruction' },\n        ],\n        [\n          { text: '💰 Баланс',          callback_data: 'account:balance' },\n          { text: '⚡️ Купить кредиты',  callback_data: 'account:buy_credits' },\n        ],\n      ],\n    };\n  } else {\n    homeInlineMarkup = {\n      inline_keyboard: [\n        [\n          { text: '⚡️ FLUX',           callback_data: 'home:flux' },\n        ],\n        [\n          { text: '📘 Instructions',   callback_data: 'home:instruction' },\n        ],\n        [\n          { text: '💰 Balance',        callback_data: 'account:balance' },\n          { text: '⚡️ Buy credits',    callback_data: 'account:buy_credits' },\n        ],\n      ],\n    };\n  }\n}\n\n// ====== Keyboard (v1: hide subscription, keep credits) ======\nlet dynNode = node;\n\n// Мягко скрываем пункт \"buy_sub\" в аккаунте (UI), логика ветки buy_sub остаётся\nif (node.key === 'account') {\n  dynNode = {\n    ...node,\n    children: (node.children || []).filter(c => c.key !== 'buy_sub')\n  };\n}\n\n// Для community показываем клавиатуру как на главном экране (root MENU),\n// но текст/placeholder остаются от самого раздела community.\nconst keyboardNode = (node.key === 'community') ? MENU : dynNode;\n\n// reply keyboard (skip when lang inline or account action)\nlet replyMarkup = undefined, replyText = undefined;\nif (!isLangTool && !isAccountAction) {\n  const rows=[];\n  const webapps=(keyboardNode.children||[]).filter(c=>c.type==='webapp');\n  for (const c of webapps) {\n    rows.push([{ text: c.title, web_app: { url: c.webUrl } }]);\n  }\n\n  const normals=(keyboardNode.children||[]).filter(c=>c.type!=='webapp');\n  for (let i=0; i<normals.length; i+=2) {\n    rows.push(normals.slice(i,i+2).map(c => ({ text: c.title })));\n  }\n\n  const nav = [];\n  // На экране community кнопку Back не показываем,\n  // чтобы клавиатура была как у home (только Home).\n  const showBack = path && node.key !== 'community';\n  if (showBack) {\n    nav.push({ text: BTN_BACK });\n  }\n  nav.push({ text: BTN_MAIN });\n  rows.push(nav);\n\n\n  replyMarkup = {\n    keyboard: rows,\n    resize_keyboard: true,\n    one_time_keyboard: false,\n    selective: false,\n    input_field_placeholder: dynNode.title || L('placeholder'),\n  };\n  replyText = (path ? `${dynNode.title}\\n${dynNode.desc||''}` : L('mainTitle')).trim();\n}\n\n\n\n// prompt fields\nconst promptText = msg.caption ?? msg.text ?? '';\n\n// media\nlet photoFileId=null, file_kind=null, file_name=null, mime_type=null;\nconst doc = msg.document;\nif (doc?.file_id) {\n  const mt = String(doc.mime_type || '').toLowerCase();\n  const name = String(doc.file_name || '').toLowerCase();\n  const looksImage = mt.startsWith('image/') || /\\.(png|jpe?g|webp|bmp|gif|tiff?)$/.test(name);\n  if (looksImage) { photoFileId=doc.file_id; file_kind='document'; file_name=doc.file_name||null; mime_type=doc.mime_type||null; }\n}\nif (!photoFileId && Array.isArray(msg.photo) && msg.photo.length>0) {\n  const best = msg.photo[msg.photo.length-1];\n  photoFileId = best.file_id; file_kind='photo';\n}\n\n// output\nreturn [{\n  chatId,\n  tool: session.tool,\n  account_action: account_action || undefined,\n  intent: isLangTool ? 'LANG_PICK' : (isAccountAction ? 'ACCOUNT_ACTION' : (isInstructionTool ? 'INSTRUCTION' : undefined)),\n\n  shouldSendMenu,\n  isPrompt,\n  replyText,\n  replyMarkup,\n  silent: true,\n  prompt: promptText,\n\n  photo_file_id: photoFileId,\n  file_kind, file_name, mime_type,\n\n  deleteChatId: chatId,\n  deleteMessageId: msgId,\n  path,\n  lang,\n  is_start: isStartCmd,\n\n  // NEW:\n  homeInlineShow,\n  homeInlineText,\n  homeInlineMarkup,\n}];\n"
        },
        "type": "n8n-nodes-base.code",
        "typeVersion": 2,
        "position": [
          -96,
          2016
        ],
        "id": "a2fa5e5b-86d9-46e8-b5e8-946bee127bcc",
        "name": "menu"
      },
      {
        "parameters": {
          "jsCode": "// build_sub_invoice — подготовка инвойса Stars (XTR) для подписки Standard\n// ВХОД: ожидаем, что сверху пришло tool:'account' и account_action:'buy_sub' + chatId/msg\n// ВЫХОД: invoice (плоские поля для createInvoiceLink), robokassa_url, тексты UI\n\nconst sd = $getWorkflowStaticData('global');\n\n// --- Вход / контекст\nconst chatId = String($json.chatId || $json.message?.chat?.id || '');\nconst msgId  = $json.deleteMessageId || $json.message?.message_id || undefined;\n\nconst tool   = $json.tool || null;\nconst action = $json.account_action || null;\n\n// Язык: по входу → кэш → подсказка Telegram → ru\nlet lang = (typeof $json.lang === 'string' && /^(ru|en)$/i.test($json.lang)) \n  ? $json.lang.toLowerCase()\n  : (sd.userLang?.[chatId] || (String($json.message?.from?.language_code || '').startsWith('en') ? 'en' : 'ru'));\nsd.userLang = sd.userLang || {};\nsd.userLang[chatId] = lang;\n\n// Фильтр: запускаемся только на покупку подписки из меню аккаунта\nif (tool !== 'account' || action !== 'buy_sub') {\n  return [{ json: { skip: true, reason: 'not_account_buy_sub' } }];\n}\n\n// --- Конфигурация цены (звёзды = XTR, целое число)\nconst PLAN_KEY = 'standard';\nconst PRICE_STARS = Number(sd.pricing?.sub?.standard_stars) || 1; // TODO: поставь свою цену в звёздах\nconst DURATION_DAYS = 30;\n\n// --- Локализация\nconst T = (k) => {\n  const RU = {\n    title: 'Подписка Стандарт',\n    desc:  'Доступ ко всем инструментам на 30 дней',\n    label: `Стандарт (${DURATION_DAYS} дней)`,\n    prompt: `Выберите способ оплаты подписки «Стандарт» на ${DURATION_DAYS} дней.\\nСтоимость: ${PRICE_STARS} ⭐️`,\n    payStars: 'Оплатить звёздами',\n    payCard: 'Оплатить картой'\n  };\n  const EN = {\n    title: 'Standard Subscription',\n    desc:  'Access to all tools for 30 days',\n    label: `Standard (${DURATION_DAYS} days)`,\n    prompt: `Choose a payment method for “Standard” ${DURATION_DAYS}-day plan.\\nPrice: ${PRICE_STARS} ⭐️`,\n    payStars: 'Pay with Stars',\n    payCard: 'Pay by card'\n  };\n  const dict = (lang === 'en') ? EN : RU;\n  return dict[k] || k;\n};\n\n// --- Параметры для createInvoiceLink (Stars/XTR не требует provider_token)\nconst payload = `sub:${PLAN_KEY}:${chatId}:${Date.now()}`;\n\n// Важно: prices должен быть JSON-строкой массива LabeledPrice ({label, amount})\nconst prices = JSON.stringify([{ label: T('label'), amount: PRICE_STARS }]);\n\nconst invoice = {\n  title: T('title'),\n  description: T('desc'),\n  payload,\n  currency: 'XTR',\n  prices,\n  // Не указываем provider_token для Stars!\n  // Дополнительно можно добавить:\n  // photo_url: 'https://.../preview.jpg',\n  // need_name/address/... — НЕ нужно для Stars\n};\n\n// Плейсхолдер для оплаты картой (Robokassa) — просто ссылка на ваш бэкенд\nconst robokassa_url = `https://pay.example/robokassa?plan=${PLAN_KEY}&uid=${encodeURIComponent(chatId)}`;\n\n// Текст сообщения, которое отправим после получения invoice_link\nconst pay_prompt_text = T('prompt');\n\n// Возвращаем все данные для следующих нод\nreturn [{\n  json: {\n    chatId,\n    // Чтобы удалить исходное сообщение пользователя\n    deleteChatId: chatId,\n    deleteMessageId: msgId,\n\n    // Для следующего шага (HTTP Request -> createInvoiceLink)\n    invoice,          // плоские поля\n    // Для сообщения-кнопок\n    robokassa_url,\n    pay_prompt_text,\n    ui_texts: {\n      payStars: T('payStars'),\n      payCard: T('payCard')\n    },\n\n    meta: { tool, action, lang, plan: PLAN_KEY, price_stars: PRICE_STARS, duration_days: DURATION_DAYS }\n  }\n}];\n"
        },
        "type": "n8n-nodes-base.code",
        "typeVersion": 2,
        "position": [
          2032,
          432
        ],
        "id": "7bb0d7a4-a8ec-49f1-b251-7e67d34424bf",
        "name": "build_sub_invoice",
        "disabled": true
      },
      {
        "parameters": {
          "jsCode": "// Ловим и парсим pre_checkout_query (в т.ч. Stars: currency === \"XTR\")\n// Если апдейт не тот — ничего не отдаём.\n\nconst pq = $json.pre_checkout_query;\nif (!pq) {\n  return [];\n}\n\n// Базовые поля\nconst id           = String(pq.id || '');\nconst fromId       = Number(pq.from?.id || 0);\nconst username     = String(pq.from?.username || '');\nconst chatId       = String(pq.chat?.id || pq.from?.id || ''); // для приватных обычно = from.id\nconst currency     = String(pq.currency || '');  // XTR для звёзд\nconst total_amount = Number(pq.total_amount || 0);\nconst payload      = String(pq.invoice_payload || ''); // то, что вы закладывали при sendInvoice\n\n// Если нужно — валидация payload (план, срок, и т.д.)\n// Здесь просто прокидываем дальше.\nreturn [{\n  json: {\n    kind: 'pre_checkout',\n    chatId,\n    userId: fromId,\n    username,\n    pre_checkout_query_id: id,\n    currency,\n    total_amount,\n    payload,\n    // Решение об ок/не ок можно принимать здесь:\n    ok: true,\n    error_message: null\n  }\n}];\n"
        },
        "type": "n8n-nodes-base.code",
        "typeVersion": 2,
        "position": [
          -912,
          -1280
        ],
        "id": "d320d2ff-a421-415a-afa1-84f967c24d2e",
        "name": "payment.precheckout.parse"
      },
      {
        "parameters": {
          "method": "POST",
          "url": "https://api.telegram.org/bot8495842221:AAHBriXvzudyazB2f8z3ilGQ8A5ESdjjtl8/createInvoiceLink",
          "sendBody": true,
          "bodyParameters": {
            "parameters": [
              {
                "name": "title",
                "value": "={{ $json.invoice.title }}"
              },
              {
                "name": "description",
                "value": "={{ $json.invoice.description }}"
              },
              {
                "name": "payload",
                "value": "={{ $json.invoice.payload }}"
              },
              {
                "name": "currency",
                "value": "={{ $json.invoice.currency }}"
              },
              {
                "name": "prices",
                "value": "={{ $json.invoice.prices }}"
              }
            ]
          },
          "options": {}
        },
        "type": "n8n-nodes-base.httpRequest",
        "typeVersion": 4.2,
        "position": [
          2336,
          432
        ],
        "id": "4279494d-0a2d-48eb-af5c-ed01d2b3f370",
        "name": "createInvoiceLink",
        "disabled": true
      },
      {
        "parameters": {
          "method": "POST",
          "url": "https://api.telegram.org/bot8495842221:AAHBriXvzudyazB2f8z3ilGQ8A5ESdjjtl8/answerPreCheckoutQuery",
          "sendBody": true,
          "bodyParameters": {
            "parameters": [
              {
                "name": "pre_checkout_query_id",
                "value": "={{ $json.pre_checkout_query_id }}"
              },
              {
                "name": "ok",
                "value": "={{ $json.ok }}"
              },
              {
                "name": "error_message",
                "value": "={{ $json.error_message }}"
              }
            ]
          },
          "options": {}
        },
        "type": "n8n-nodes-base.httpRequest",
        "typeVersion": 4.2,
        "position": [
          -640,
          -1280
        ],
        "id": "b50f9952-0532-40f3-ad19-7c47386d768e",
        "name": "answerPreCheckoutQuery"
      },
      {
        "parameters": {
          "jsCode": "/**\n * Parse Telegram successful_payment → DB payload + ACK\n * Подключение:\n *  1) HTTP → Supabase (используй $json.db_event)\n *  2) Telegram → sendMessage (chat_id = $json.ack.chat_id, text = $json.ack.text)\n */\n\nconst sd = $getWorkflowStaticData('global');\nsd.userLang = sd.userLang || {};\nsd.payments = sd.payments || {}; // идемпотентность по charge_id\n\n// ---- Input\nconst upd = $json || {};\nconst msg = upd.message || {};\nconst sp  = msg.successful_payment || {};\n\nconst chatId  = String(msg.chat?.id || '');\nconst userId  = Number(msg.from?.id || chatId || 0);\nconst uname   = String(msg.from?.username || '');\nconst fname   = String([msg.from?.first_name, msg.from?.last_name].filter(Boolean).join(' ') || '');\nconst lang    = (sd.userLang[chatId] || (/^en/i.test(String(msg.from?.language_code||'')) ? 'en' : 'ru'));\n\nconst currency = String(sp.currency || 'XTR');\nconst amount   = Number(sp.total_amount || 0);\nconst payload_raw = String(sp.invoice_payload || '');\nconst tg_charge_id = String(sp.telegram_payment_charge_id || '');\nconst provider_charge_id = String(sp.provider_payment_charge_id || '');\nconst paid_at = new Date().toISOString();\n\n// ---- Safe date utils\nfunction addDays(baseIso, days){\n  const base = baseIso ? new Date(baseIso) : new Date();\n  if (!Number.isFinite(days)) return null;\n  if (isNaN(base.getTime())) return null;\n  base.setUTCDate(base.getUTCDate() + days);\n  try { return base.toISOString(); } catch { return null; }\n}\nfunction toHuman(iso, lang){\n  if (!iso) return '—';\n  const d = new Date(iso);\n  if (isNaN(d.getTime())) return '—';\n  try {\n    const loc = lang === 'en' ? 'en' : 'ru';\n    return d.toLocaleString(loc, {\n      year:'numeric', month:'2-digit', day:'2-digit',\n      hour:'2-digit', minute:'2-digit'\n    });\n  } catch {\n    const pad = n => String(n).padStart(2,'0');\n    return `${d.getUTCFullYear()}-${pad(d.getUTCMonth()+1)}-${pad(d.getUTCDate())} ${pad(d.getUTCHours())}:${pad(d.getUTCMinutes())} UTC`;\n  }\n}\n\n// ---- Parse payload (сохраняем nonce, но не используем его для срока)\nfunction parsePayload(raw){\n  const out = { raw, type:'unknown', plan:null, credits:null, user:null, extra:{} };\n  if (!raw) return out;\n  const parts = String(raw).split(':');\n  const head  = (parts[0]||'').toLowerCase();\n\n  if (['sub','subscription'].includes(head)) {\n    out.type = 'subscription';\n    out.plan = parts[1] || 'standard';\n    out.user = Number(parts[2] || userId) || null;\n    out.extra.nonce = parts[3] || null; // timestamp/nonce из payload, НЕ срок\n  } else if (['credits','cr'].includes(head)) {\n    out.type = 'credits';\n    const maybe = parts[1] || '';\n    const n = Number(maybe);\n    out.credits = Number.isFinite(n) && n>0 ? n : (parseInt(String(maybe).replace(/\\D+/g,''),10) || null);\n    out.plan = parts[1] || 'pack';\n  }\n  return out;\n}\nconst pp = parsePayload(payload_raw);\n\n// ---- Idempotency\nlet alreadyProcessed = false;\nif (tg_charge_id) {\n  if (sd.payments[tg_charge_id]) alreadyProcessed = true;\n  sd.payments[tg_charge_id] = true;\n}\n\n// ---- Business rules (жёстко: подписка = 30 дней и +270 кредитов)\nlet product_kind = pp.type;                 // 'subscription' | 'credits' | 'unknown'\nlet product_code = pp.plan || 'standard';\nlet term_days    = null;\nlet credits_add  = pp.credits;\n\nif (product_kind === 'subscription') {\n  term_days   = 30;       // фиксированный срок\n  credits_add = 270;      // фиксированное начисление\n}\n\n// Ожидаемая дата окончания\nconst expected_until_iso = (product_kind === 'subscription' && term_days)\n  ? addDays(paid_at, term_days)\n  : null;\n\n// ---- DB payload\nconst db_event = {\n  tg_user_id: userId,\n  chat_id: chatId,\n  username: uname,\n  full_name: fname,\n  currency,\n  amount,\n  payload_raw,\n  payload_type: product_kind,\n  plan: product_code,\n  term: null,\n  term_days: term_days,\n  credits: credits_add,\n  lang,\n  telegram_payment_charge_id: tg_charge_id,\n  provider_payment_charge_id: provider_charge_id,\n  paid_at,\n  expected_until: expected_until_iso\n};\n\n// ---- ACK text\nlet ackLines = [];\nif (lang === 'en') {\n  ackLines.push(alreadyProcessed ? 'ℹ️ Payment was already processed.' : '✅ Payment received.');\n  if (product_kind === 'subscription') {\n    ackLines.push(`Type: subscription (${product_code})`);\n    ackLines.push(`Term: ${term_days} days`);\n    ackLines.push(`Credits: +${credits_add}`);\n    if (expected_until_iso) ackLines.push(`Valid till (expected): ${toHuman(expected_until_iso,'en')}`);\n  } else if (product_kind === 'credits') {\n    ackLines.push(`Type: credits`);\n    ackLines.push(`Credits: +${credits_add ?? '—'}`);\n  } else {\n    ackLines.push(`Type: ${product_kind}`);\n  }\n  ackLines.push(`Amount: ${amount} ${currency}`);\n  if (tg_charge_id) ackLines.push(`Receipt: ${tg_charge_id}`);\n} else {\n  ackLines.push(alreadyProcessed ? 'ℹ️ Платёж уже был обработан.' : '✅ Оплата получена.');\n  if (product_kind === 'subscription') {\n    ackLines.push(`Тип: подписка (${product_code})`);\n    ackLines.push(`Срок: ${term_days} дн.`);\n    ackLines.push(`Кредиты: +${credits_add}`);\n    if (expected_until_iso) ackLines.push(`Действует до (ожид.): ${toHuman(expected_until_iso,'ru')}`);\n  } else if (product_kind === 'credits') {\n    ackLines.push(`Тип: кредиты`);\n    ackLines.push(`Кредиты: +${credits_add ?? '—'}`);\n  } else {\n    ackLines.push(`Тип: ${product_kind}`);\n  }\n  ackLines.push(`Сумма: ${amount} ${currency}`);\n  if (tg_charge_id) ackLines.push(`Чек: ${tg_charge_id}`);\n}\n\nconst ack = {\n  chat_id: chatId,\n  text: ackLines.join('\\n'),\n  disable_notification: true,\n};\n\n// ---- Return\nreturn [{\n  json: {\n    ok: true,\n    kind: 'payment_success',\n    chatId,\n    userId,\n    lang,\n    db_event,\n    ack\n  }\n}];\n"
        },
        "type": "n8n-nodes-base.code",
        "typeVersion": 2,
        "position": [
          -912,
          -1072
        ],
        "id": "f971aae9-adcd-460e-b37d-5e94e6a8e6f7",
        "name": "payment.parse_success"
      },
      {
        "parameters": {
          "method": "POST",
          "url": "=https://bwbsclwdkighhzlyiman.supabase.co/rest/v1/rpc/apply_telegram_payment",
          "authentication": "predefinedCredentialType",
          "nodeCredentialType": "supabaseApi",
          "sendHeaders": true,
          "headerParameters": {
            "parameters": [
              {
                "name": "Content-Type",
                "value": " application/json"
              }
            ]
          },
          "sendBody": true,
          "specifyBody": "json",
          "jsonBody": "={\n  \"p_tg_user_id\": {{ $json.userId }},\n  \"p_provider\": \"telegram-stars\",\n  \"p_charge_id\": \"{{ $json.db_event.provider_payment_charge_id }}\",\n  \"p_currency\": \"{{ $json.db_event.currency }}\",\n  \"p_amount\": {{ $json.db_event.amount }},\n  \"p_product_kind\": \"{{ $json.db_event.payload_type || 'unknown' }}\",\n  \"p_product_code\": \"{{ $json.db_event.plan || '' }}\",\n  \"p_credits_added\": {{ $json.db_event.credits || 0 }},\n  \"p_plan\": \"{{ $json.db_event.payload_type === 'subscription'\n        ? ($json.db_event.plan || 'standard')\n        : null }}\",\n  \"p_plan_days\": {{ $json.db_event.payload_type === 'subscription'\n        ? ($json.db_event.term_days || 0)\n        : 0 }},\n  \"p_lang\": \"{{ $json.lang || $json.db_event.lang || 'ru' }}\"\n}\n\n",
          "options": {}
        },
        "type": "n8n-nodes-base.httpRequest",
        "typeVersion": 4.2,
        "position": [
          -640,
          -1072
        ],
        "id": "e9bb008d-dcb9-4b6b-bc08-f67041f48bb1",
        "name": "apply_payment",
        "credentials": {
          "supabaseApi": {
            "id": "jGgpXKPYHiL193Rz",
            "name": "Supabase account"
          }
        }
      },
      {
        "parameters": {
          "rules": {
            "values": [
              {
                "conditions": {
                  "options": {
                    "caseSensitive": true,
                    "leftValue": "",
                    "typeValidation": "strict",
                    "version": 2
                  },
                  "conditions": [
                    {
                      "id": "58c08747-c6af-4557-9934-fd5bcba58813",
                      "leftValue": "={{ $json.pre_checkout_query }}",
                      "rightValue": "",
                      "operator": {
                        "type": "object",
                        "operation": "exists",
                        "singleValue": true
                      }
                    }
                  ],
                  "combinator": "and"
                }
              },
              {
                "conditions": {
                  "options": {
                    "caseSensitive": true,
                    "leftValue": "",
                    "typeValidation": "strict",
                    "version": 2
                  },
                  "conditions": [
                    {
                      "id": "d0bf0e9a-81b2-4be3-b897-f8b1c405c5c0",
                      "leftValue": "={{ $json.message.successful_payment }}",
                      "rightValue": "",
                      "operator": {
                        "type": "object",
                        "operation": "exists",
                        "singleValue": true
                      }
                    }
                  ],
                  "combinator": "and"
                }
              },
              {
                "conditions": {
                  "options": {
                    "caseSensitive": true,
                    "leftValue": "",
                    "typeValidation": "strict",
                    "version": 2
                  },
                  "conditions": [
                    {
                      "id": "6337ea59-4635-406e-89d4-62505dae4f35",
                      "leftValue": "={{ $json.callback_query }}",
                      "rightValue": "gpt",
                      "operator": {
                        "type": "object",
                        "operation": "exists",
                        "singleValue": true
                      }
                    }
                  ],
                  "combinator": "and"
                }
              },
              {
                "conditions": {
                  "options": {
                    "caseSensitive": true,
                    "leftValue": "",
                    "typeValidation": "strict",
                    "version": 2
                  },
                  "conditions": [
                    {
                      "id": "d6fc6a81-7010-4119-9d91-c66d79e68ac8",
                      "leftValue": "={{ $json.callback_query }}",
                      "rightValue": "=flux",
                      "operator": {
                        "type": "object",
                        "operation": "notExists",
                        "singleValue": true
                      }
                    }
                  ],
                  "combinator": "and"
                }
              }
            ]
          },
          "options": {}
        },
        "type": "n8n-nodes-base.switch",
        "typeVersion": 3.2,
        "position": [
          -1328,
          1920
        ],
        "id": "d8fd6414-7431-4c53-9815-32a6e54887bf",
        "name": "Switch_message_type"
      },
      {
        "parameters": {
          "rules": {
            "values": [
              {
                "conditions": {
                  "options": {
                    "caseSensitive": true,
                    "leftValue": "",
                    "typeValidation": "strict",
                    "version": 2
                  },
                  "conditions": [
                    {
                      "id": "6337ea59-4635-406e-89d4-62505dae4f35",
                      "leftValue": "={{ $json.message.web_app_data }}",
                      "rightValue": "gpt",
                      "operator": {
                        "type": "object",
                        "operation": "exists",
                        "singleValue": true
                      }
                    }
                  ],
                  "combinator": "and"
                }
              },
              {
                "conditions": {
                  "options": {
                    "caseSensitive": true,
                    "leftValue": "",
                    "typeValidation": "strict",
                    "version": 2
                  },
                  "conditions": [
                    {
                      "id": "d6fc6a81-7010-4119-9d91-c66d79e68ac8",
                      "leftValue": "={{ $json.message.web_app_data }}",
                      "rightValue": "=flux",
                      "operator": {
                        "type": "object",
                        "operation": "notExists",
                        "singleValue": true
                      }
                    }
                  ],
                  "combinator": "and"
                }
              }
            ]
          },
          "options": {}
        },
        "type": "n8n-nodes-base.switch",
        "typeVersion": 3.2,
        "position": [
          -320,
          2016
        ],
        "id": "878f23f7-8821-4e12-8edd-7034e1b4de40",
        "name": "Switch_web_app"
      },
      {
        "parameters": {
          "rules": {
            "values": [
              {
                "conditions": {
                  "options": {
                    "caseSensitive": true,
                    "leftValue": "",
                    "typeValidation": "strict",
                    "version": 2
                  },
                  "conditions": [
                    {
                      "id": "6337ea59-4635-406e-89d4-62505dae4f35",
                      "leftValue": "={{ $json.shouldSendMenu }}",
                      "rightValue": "gpt",
                      "operator": {
                        "type": "boolean",
                        "operation": "true",
                        "singleValue": true
                      }
                    }
                  ],
                  "combinator": "and"
                }
              },
              {
                "conditions": {
                  "options": {
                    "caseSensitive": true,
                    "leftValue": "",
                    "typeValidation": "strict",
                    "version": 2
                  },
                  "conditions": [
                    {
                      "id": "d6fc6a81-7010-4119-9d91-c66d79e68ac8",
                      "leftValue": "={{ $json.shouldSendMenu }}",
                      "rightValue": "=flux",
                      "operator": {
                        "type": "boolean",
                        "operation": "false",
                        "singleValue": true
                      }
                    }
                  ],
                  "combinator": "and"
                }
              }
            ]
          },
          "options": {}
        },
        "type": "n8n-nodes-base.switch",
        "typeVersion": 3.2,
        "position": [
          400,
          2016
        ],
        "id": "c7049219-c441-46c6-a96f-d06cd341662d",
        "name": "switch_reply_menu"
      },
      {
        "parameters": {
          "jsCode": "// FIRST FUNCTION: parse web_app_data & save settings (FLUX + SEEDANCE + SEEDREAM + VEO + SORA)\nconst sd = $getWorkflowStaticData('global');\nsd.flux      = sd.flux      || {}; // { [chatId]: { ar, ref, seed, quality, useRef, savedAt } }\nsd.seedance  = sd.seedance  || {}; // { [chatId]: { mode,tier,model,i2v_mode,resolution,ratio,duration,camerafixed,savedAt } }\nsd.seedream  = sd.seedream  || {}; // { [chatId]: { quality, ratio, dims_t2i:{w,h}, long_edge_i2i, savedAt } }\nsd.veo       = sd.veo       || {}; // { [chatId]: { model, ratio, mode, savedAt } }\nsd.sora      = sd.sora      || {}; // { [chatId]: { model, ratio, duration, savedAt } }  <-- NEW\n\nconst msg = $json.message || {};\nconst chatId = String(msg.chat?.id || $json.chat?.id || '');\nconst wad = msg.web_app_data?.data;   // JSON-строка из Telegram WebApp\n\nlet saved = false;\nlet kind  = null;   // 'flux' | 'seedance' | 'seedream' | 'veo' | 'sora'\nlet savedCfg = null;\nlet err = null;\n\nfunction clamp(n, lo, hi) { return Math.min(hi, Math.max(lo, n)); }\n\ntry {\n  if (chatId && wad) {\n    const raw = JSON.parse(wad);\n\n    // ======== FLUX ========\n    if (raw?.type === 'flux_settings') {\n      kind = 'flux';\n\n      const cleanAr = String(raw.ar ?? '1:1').replace(/\\s+/g, '');\n      const ar = /^\\d+:\\d+$/.test(cleanAr) ? cleanAr : '1:1';\n\n      let ref = Number(raw.ref);\n      if (!Number.isFinite(ref)) ref = 0.75;\n      ref = clamp(ref, 0, 1.2);\n\n      const seed = raw.random ? 0 : (Number(raw.seed) || 0);\n      const qNum = Number(raw.quality);\n      const bNum = Number(raw.batch);\n      const quality = [0,1,2].includes(qNum) ? qNum : 1;\n      const batch = [1,2,4].includes(bNum) ? bNum : 1;\n      \n      const useRef = Boolean(raw.useRef ?? raw.use_ref ?? false);\n\n      const cfg = { ar, ref, seed, quality, batch, useRef, savedAt: new Date().toISOString() };\n      sd.flux[chatId] = cfg;\n      saved = true;\n      savedCfg = cfg;\n    }\n\n    // ======== SEEDANCE ========\n    if (raw?.type === 'seedance_settings') {\n      kind = 'seedance';\n\n      const mode = /^(i2v|t2v)$/.test(String(raw.mode)) ? raw.mode : 'i2v';\n      const tier = /^(lite|pro)$/.test(String(raw.tier)) ? raw.tier : 'lite';\n\n      const i2v_mode = /^(first|last|first_last|reference)$/.test(String(raw.i2v_mode))\n        ? raw.i2v_mode : 'first';\n\n      const MODEL_IDS = {\n        lite: {\n          i2v: 'bytedance-seedance-1-0-lite-i2v-250428',\n          t2v: 'bytedance-seedance-1-0-lite-t2v-250428',\n        },\n        pro: {\n          i2v: null,\n          t2v: null,\n        }\n      };\n      const fallbackModel = MODEL_IDS?.[tier]?.[mode] || null;\n      const model = String(raw.model || fallbackModel || '');\n\n      const resolution = /^(480p|720p)$/i.test(String(raw.resolution)) ? raw.resolution.toLowerCase() : '480p';\n\n      const R = new Set(['auto','16:9','4:3','1:1','3:4','9:16','21:9']);\n      const ratio = R.has(String(raw.ratio)) ? String(raw.ratio) : 'auto';\n\n      let duration = Number(raw.duration);\n      duration = (duration === 5 || duration === 10) ? duration : 5;\n\n      const camerafixed = Boolean(raw.camerafixed);\n\n      const cfg = {\n        mode, tier, model,\n        i2v_mode,\n        resolution, ratio, duration, camerafixed,\n        savedAt: new Date().toISOString()\n      };\n      sd.seedance[chatId] = cfg;\n      saved = true;\n      savedCfg = cfg;\n    }\n\n    // ======== SEEDREAM (image gen) ========\n    if (raw?.type === 'seedream_settings') {\n      kind = 'seedream';\n\n      let quality = String(raw.quality || '2k').toLowerCase();\n      if (!['1k','2k','4k'].includes(quality)) quality = '2k';\n\n      const R2 = new Set(['1:1','4:3','3:4','16:9','9:16','3:2','2:3','21:9']);\n      let ratio = String(raw.ratio || '1:1');\n      if (!R2.has(ratio)) ratio = '1:1';\n\n      const BASE_2K = {\n        '1:1':  { w: 2048, h: 2048 },\n        '4:3':  { w: 2304, h: 1728 },\n        '3:4':  { w: 1728, h: 2304 },\n        '16:9': { w: 2560, h: 1440 },\n        '9:16': { w: 1440, h: 2560 },\n        '3:2':  { w: 2496, h: 1664 },\n        '2:3':  { w: 1664, h: 2496 },\n        '21:9': { w: 3024, h: 1296 },\n      };\n\n      const scale = (quality === '1k') ? 0.5 : (quality === '4k') ? 2 : 1;\n      const base = BASE_2K[ratio] || BASE_2K['1:1'];\n      const dims_t2i = { w: Math.round(base.w * scale), h: Math.round(base.h * scale) };\n      const long_edge_i2i = (quality === '1k') ? 1024 : (quality === '4k' ? 4096 : 2048);\n\n      const cfg = { quality, ratio, dims_t2i, long_edge_i2i, savedAt: new Date().toISOString() };\n      sd.seedream[chatId] = cfg;\n      saved = true;\n      savedCfg = cfg;\n    }\n\n    // ======== VEO (video gen) ========\n    if (raw?.type === 'veo_settings') {\n      kind = 'veo';\n\n      let model = String(raw.model || 'veo3.1').toLowerCase();\n      if (!/^veo/.test(model)) model = 'veo3.1';\n\n      let ratio = String(raw.ratio || '').trim();\n      if (!/^(16:9|9:16)$/.test(ratio)) ratio = 'auto';\n\n      let mode = String(raw.mode || 't2v');\n      if (!/^(t2v|i2v|first_last|reference)$/.test(mode)) mode = 't2v';\n\n      const cfg = { model, ratio, mode, savedAt: new Date().toISOString() };\n      sd.veo[chatId] = cfg;\n      saved = true;\n      savedCfg = cfg;\n    }\n\n    // ======== SORA (video gen) — NEW ========\n    if (raw?.type === 'sora_settings') {\n      kind = 'sora';\n\n      // model: sora-2-pro | sora-2-hd | sora-2\n      const ALLOWED = new Set(['sora-2-pro','sora-2-hd','sora-2']);\n      let model = String(raw.model || 'sora-2').toLowerCase();\n      if (!ALLOWED.has(model)) model = 'sora-2';\n\n      // ratio: 16:9 | 9:16\n      let ratio = String(raw.ratio || '16:9');\n      if (!/^(16:9|9:16)$/.test(ratio)) ratio = '16:9';\n\n      // duration: 4 | 8 | 12\n      let duration = Number(raw.duration);\n      duration = [4,8,12].includes(duration) ? duration : 8;\n\n      const cfg = { model, ratio, duration, savedAt: new Date().toISOString() };\n      sd.sora[chatId] = cfg;\n      saved = true;\n      savedCfg = cfg;\n    }\n  }\n} catch (e) {\n  err = String(e?.message || e);\n}\n\nreturn [{\n  json: {\n    chatId,\n    hasWebAppData: Boolean(wad),\n    saved,\n    kind,          // 'flux' | 'seedance' | 'seedream' | 'veo' | 'sora' | null\n    savedCfg,\n    errorMsg: err || null,\n    deleteChatId: chatId,\n    deleteMessageId: msg.message_id\n  }\n}];\n"
        },
        "type": "n8n-nodes-base.code",
        "typeVersion": 2,
        "position": [
          -96,
          1808
        ],
        "id": "bfc9c533-bb3d-465c-b2c4-e97efae0a3bf",
        "name": "parse web_app_data"
      },
      {
        "parameters": {
          "chatId": "={{ $json.chatId }}",
          "text": "={{ $json.lang === 'en' ? '🌐 Choose your language:' : '🌐 Выберите язык:' }}",
          "replyMarkup": "inlineKeyboard",
          "inlineKeyboard": {
            "rows": [
              {
                "row": {
                  "buttons": [
                    {
                      "text": "Русский ",
                      "additionalFields": {
                        "callback_data": "setlang:ru"
                      }
                    },
                    {
                      "text": "English ",
                      "additionalFields": {
                        "callback_data": "setlang:en"
                      }
                    }
                  ]
                }
              }
            ]
          },
          "additionalFields": {
            "appendAttribution": false
          }
        },
        "id": "42b24f4b-6d0b-4bf9-b679-15699f28dac9",
        "name": "lang_inline",
        "type": "n8n-nodes-base.telegram",
        "typeVersion": 1.2,
        "position": [
          2016,
          992
        ],
        "webhookId": "2b9cbcf8-d73a-4d63-ba56-8fac5109a862",
        "credentials": {
          "telegramApi": {
            "id": "ur7jSUPdiAaPVhCf",
            "name": "Geni AI"
          }
        }
      },
      {
        "parameters": {
          "operation": "deleteMessage",
          "chatId": "={{ $json.result.chat.id }}",
          "messageId": "={{ $('Switch1').item.json.deleteMessageId }}"
        },
        "id": "d7a6d677-9096-4f04-af9b-6cbf64816edb",
        "name": "delMes",
        "type": "n8n-nodes-base.telegram",
        "typeVersion": 1.2,
        "position": [
          2224,
          992
        ],
        "webhookId": "7e67278c-ca08-4b2b-ade0-5fbc6c1e9c14",
        "credentials": {
          "telegramApi": {
            "id": "ur7jSUPdiAaPVhCf",
            "name": "Geni AI"
          }
        },
        "onError": "continueRegularOutput"
      },
      {
        "parameters": {
          "operation": "deleteMessage",
          "chatId": "={{ $('Switch1').item.json.chatId }}",
          "messageId": "={{ $('Switch1').item.json.deleteMessageId }}"
        },
        "id": "6cff3a8a-d1ce-4931-a54d-84be2051dd0b",
        "name": "delMes1",
        "type": "n8n-nodes-base.telegram",
        "typeVersion": 1.2,
        "position": [
          2576,
          176
        ],
        "webhookId": "72e170d1-8ab7-402e-84e2-57ed36c830ef",
        "credentials": {
          "telegramApi": {
            "id": "ur7jSUPdiAaPVhCf",
            "name": "Geni AI"
          }
        }
      },
      {
        "parameters": {
          "operation": "deleteMessage",
          "chatId": "={{ $json.chatId }}",
          "messageId": "={{ $json.targetMsgId }}"
        },
        "id": "c5fc35e3-5029-49ff-bdbf-4e40b5ed2c5a",
        "name": "delMes_targetMsg",
        "type": "n8n-nodes-base.telegram",
        "typeVersion": 1.2,
        "position": [
          -448,
          -480
        ],
        "webhookId": "263a1d6e-2aab-45a0-ac0a-fd4f61fe227d",
        "credentials": {
          "telegramApi": {
            "id": "ur7jSUPdiAaPVhCf",
            "name": "Geni AI"
          }
        }
      },
      {
        "parameters": {
          "operation": "deleteMessage",
          "chatId": "={{ $json.chatId }}",
          "messageId": "={{ $json.keyboardMsgId }}"
        },
        "id": "78fbf151-84d7-4f87-9277-73c25acf66ae",
        "name": "delMes_keyboardMsg",
        "type": "n8n-nodes-base.telegram",
        "typeVersion": 1.2,
        "position": [
          -240,
          -480
        ],
        "webhookId": "bfdf4554-b218-4367-b05c-4a3a963e8692",
        "credentials": {
          "telegramApi": {
            "id": "ur7jSUPdiAaPVhCf",
            "name": "Geni AI"
          }
        }
      },
      {
        "parameters": {
          "operation": "deleteMessage",
          "chatId": "={{ $('message_reply_inline').item.json.chatId }}",
          "messageId": "={{ $('message_reply_inline').item.json.keyboardMsgId }}"
        },
        "id": "cdd2a43d-31ab-464e-970c-78ffcfbfa81f",
        "name": "delMes_keyboardMsg1",
        "type": "n8n-nodes-base.telegram",
        "typeVersion": 1.2,
        "position": [
          -240,
          32
        ],
        "webhookId": "e7190172-8910-40d7-8c25-51328f01d3d9",
        "credentials": {
          "telegramApi": {
            "id": "ur7jSUPdiAaPVhCf",
            "name": "Geni AI"
          }
        }
      },
      {
        "parameters": {
          "rules": {
            "values": [
              {
                "conditions": {
                  "options": {
                    "caseSensitive": true,
                    "leftValue": "",
                    "typeValidation": "strict",
                    "version": 2
                  },
                  "conditions": [
                    {
                      "id": "78542b6d-15c2-4809-935c-49d1e17dcc1e",
                      "leftValue": "={{ $json.account_action }}",
                      "rightValue": "balance",
                      "operator": {
                        "type": "string",
                        "operation": "equals",
                        "name": "filter.operator.equals"
                      }
                    }
                  ],
                  "combinator": "and"
                }
              },
              {
                "conditions": {
                  "options": {
                    "caseSensitive": true,
                    "leftValue": "",
                    "typeValidation": "strict",
                    "version": 2
                  },
                  "conditions": [
                    {
                      "id": "dfbae9da-3cc7-488e-9f71-f21a4b9b4345",
                      "leftValue": "={{ $json.account_action }}",
                      "rightValue": "status",
                      "operator": {
                        "type": "string",
                        "operation": "equals",
                        "name": "filter.operator.equals"
                      }
                    }
                  ],
                  "combinator": "and"
                }
              },
              {
                "conditions": {
                  "options": {
                    "caseSensitive": true,
                    "leftValue": "",
                    "typeValidation": "strict",
                    "version": 2
                  },
                  "conditions": [
                    {
                      "id": "6337ea59-4635-406e-89d4-62505dae4f35",
                      "leftValue": "={{ $json.account_action }}",
                      "rightValue": "buy_sub",
                      "operator": {
                        "type": "string",
                        "operation": "equals"
                      }
                    }
                  ],
                  "combinator": "and"
                }
              },
              {
                "conditions": {
                  "options": {
                    "caseSensitive": true,
                    "leftValue": "",
                    "typeValidation": "strict",
                    "version": 2
                  },
                  "conditions": [
                    {
                      "id": "d6fc6a81-7010-4119-9d91-c66d79e68ac8",
                      "leftValue": "={{ $json.account_action }}",
                      "rightValue": "=buy_credits",
                      "operator": {
                        "type": "string",
                        "operation": "equals"
                      }
                    }
                  ],
                  "combinator": "and"
                }
              }
            ]
          },
          "options": {}
        },
        "type": "n8n-nodes-base.switch",
        "typeVersion": 3.2,
        "position": [
          1760,
          304
        ],
        "id": "109d52d5-e6c4-4550-8072-79dd6e29ae30",
        "name": "Switch_lang_act"
      },
      {
        "parameters": {
          "method": "POST",
          "url": "https://api.telegram.org/bot8495842221:AAHBriXvzudyazB2f8z3ilGQ8A5ESdjjtl8/deleteMessage",
          "sendBody": true,
          "bodyParameters": {
            "parameters": [
              {
                "name": "chat_id",
                "value": "={{ $('menu').item.json.chatId }}"
              },
              {
                "name": "message_id",
                "value": "={{ $('menu').item.json.deleteMessageId }}"
              }
            ]
          },
          "options": {}
        },
        "type": "n8n-nodes-base.httpRequest",
        "typeVersion": 4.2,
        "position": [
          816,
          1792
        ],
        "id": "21afacf0-2d14-4d4a-a3a3-db692b94eec7",
        "name": "deleteMessageMenu",
        "onError": "continueRegularOutput"
      },
      {
        "parameters": {
          "jsCode": "const sd = $getWorkflowStaticData('global');\nsd.userLang = sd.userLang || {};\n\n// Инлайн-колбэк от Telegram\nconst cq = $json.callback_query || {};\nconst dataRaw = cq.data;\nconst data = typeof dataRaw === 'string'\n  ? dataRaw.trim()\n  : String(dataRaw || '').trim();\n\nconst chatId = String(cq.message?.chat?.id || '');\nconst keyboardMsgId = cq.message?.message_id; // наше сообщение с инлайнами\n\nlet action = null;        // 'del_one' | 'del_all' | 'setlang' | 'buy_credits_pack'\nlet targetMsgId = null;   // для del_one\nlet pack_credits = null;  // для buy_credits_pack\nlet lang = sd.userLang[chatId] || null; // 'ru' | 'en', если уже выбран язык\n\n// ---------- разбор callback_data ----------\n// Форматы, которые сейчас используются:\n//  - \"del:12345\"      → удалить одно сообщение\n//  - \"del_all\"        → очистить все\n//  - \"setlang:ru|en\"  → смена языка\n//  - \"credits:200\"    → выбор пакета кредитов (200/500/1000)\n\nif (data.startsWith('del:')) {\n  // Удалить одно сообщение\n  action = 'del_one';\n  const idStr = data.split(':')[1];\n  const n = Number(idStr);\n  if (Number.isFinite(n)) targetMsgId = n;\n\n} else if (data === 'del_all') {\n  // Удалить все\n  action = 'del_all';\n\n} else if (data.startsWith('setlang:')) {\n  // Смена языка\n  action = 'setlang';\n  const tail = data.split(':')[1]?.trim().toLowerCase();\n  if (tail === 'ru' || tail === 'en') {\n    lang = tail;\n  }\n\n} else if (data.startsWith('credits:')) {\n  // Выбор пакета кредитов\n  // callback_data: \"credits:200\" | \"credits:500\" | \"credits:1000\"\n  action = 'buy_credits_pack';\n  const tail = data.split(':')[1]?.trim();\n  const n = Number(tail);\n  if (Number.isFinite(n) && n > 0) {\n    pack_credits = n;\n  }\n}\n\n// Валидируем язык: по умолчанию 'ru'\nif (!['ru', 'en'].includes(lang)) {\n  lang = 'ru';\n}\n\n// Подстраховка для del_one — если id не пришёл в callback_data,\n// попробуем взять из reply_to_message\nif (!targetMsgId && cq.message?.reply_to_message?.message_id) {\n  targetMsgId = cq.message.reply_to_message.message_id;\n}\n\nreturn [{\n  json: {\n    action,                // 'del_one' | 'del_all' | 'setlang' | 'buy_credits_pack'\n    lang,                  // язык (особенно важен при setlang)\n    chatId,\n    targetMsgId,           // для del_one\n    keyboardMsgId,         // id сообщения с инлайн-кнопками\n    callback_query_id: cq.id,\n    pack_credits           // для buy_credits_pack (200/500/1000 или null)\n  }\n}];\n"
        },
        "type": "n8n-nodes-base.code",
        "typeVersion": 2,
        "position": [
          -912,
          -192
        ],
        "id": "849ffd5b-8842-4b3a-83c4-7a6e85965b67",
        "name": "message_reply_inline"
      },
      {
        "parameters": {
          "rules": {
            "values": [
              {
                "conditions": {
                  "options": {
                    "caseSensitive": true,
                    "leftValue": "",
                    "typeValidation": "strict",
                    "version": 2
                  },
                  "conditions": [
                    {
                      "id": "6337ea59-4635-406e-89d4-62505dae4f35",
                      "leftValue": "={{ $json.action }}",
                      "rightValue": "del_one",
                      "operator": {
                        "type": "string",
                        "operation": "equals"
                      }
                    }
                  ],
                  "combinator": "and"
                }
              },
              {
                "conditions": {
                  "options": {
                    "caseSensitive": true,
                    "leftValue": "",
                    "typeValidation": "strict",
                    "version": 2
                  },
                  "conditions": [
                    {
                      "id": "d6fc6a81-7010-4119-9d91-c66d79e68ac8",
                      "leftValue": "={{ $json.action }}",
                      "rightValue": "=del_all",
                      "operator": {
                        "type": "string",
                        "operation": "equals"
                      }
                    }
                  ],
                  "combinator": "and"
                }
              },
              {
                "conditions": {
                  "options": {
                    "caseSensitive": true,
                    "leftValue": "",
                    "typeValidation": "strict",
                    "version": 2
                  },
                  "conditions": [
                    {
                      "id": "df33815a-03c8-4a3a-a25f-0a4a7e266f1a",
                      "leftValue": "={{ $json.action }}",
                      "rightValue": "setlang",
                      "operator": {
                        "type": "string",
                        "operation": "equals",
                        "name": "filter.operator.equals"
                      }
                    }
                  ],
                  "combinator": "and"
                }
              },
              {
                "conditions": {
                  "options": {
                    "caseSensitive": true,
                    "leftValue": "",
                    "typeValidation": "strict",
                    "version": 2
                  },
                  "conditions": [
                    {
                      "id": "fed3019b-9611-47cc-969b-c87ddce299c7",
                      "leftValue": "={{ $json.action }}",
                      "rightValue": "buy_credits_pack",
                      "operator": {
                        "type": "string",
                        "operation": "equals",
                        "name": "filter.operator.equals"
                      }
                    }
                  ],
                  "combinator": "and"
                }
              }
            ]
          },
          "options": {}
        },
        "type": "n8n-nodes-base.switch",
        "typeVersion": 3.2,
        "position": [
          -704,
          -224
        ],
        "id": "1ff60860-9caf-4e9c-8cf7-af9dc606b8f6",
        "name": "Switch_inline_act"
      },
      {
        "parameters": {
          "chatId": "={{ $('payment.parse_success').item.json.chatId }}",
          "text": "={{ $('payment.parse_success').item.json.ack.text }}",
          "additionalFields": {
            "appendAttribution": false,
            "parse_mode": "HTML"
          }
        },
        "id": "5713ca55-a40c-4c5e-a773-37ef474b0840",
        "name": "send_mes_payment",
        "type": "n8n-nodes-base.telegram",
        "typeVersion": 1.2,
        "position": [
          -384,
          -1072
        ],
        "webhookId": "c3fe8895-404b-4fa5-a0c8-46733c06592f",
        "credentials": {
          "telegramApi": {
            "id": "ur7jSUPdiAaPVhCf",
            "name": "Geni AI"
          }
        }
      },
      {
        "parameters": {
          "method": "POST",
          "url": "=https://bwbsclwdkighhzlyiman.supabase.co/rest/v1/rpc/set_user_lang",
          "authentication": "predefinedCredentialType",
          "nodeCredentialType": "supabaseApi",
          "sendHeaders": true,
          "headerParameters": {
            "parameters": [
              {
                "name": "Content-Type",
                "value": " application/json"
              }
            ]
          },
          "sendBody": true,
          "specifyBody": "json",
          "jsonBody": "={\n  \"p_tg_user_id\": {{ $json.chatId }},\n  \"p_lang\": \"{{ $json.lang }}\"\n}",
          "options": {}
        },
        "type": "n8n-nodes-base.httpRequest",
        "typeVersion": 4.2,
        "position": [
          -464,
          32
        ],
        "id": "8e6d0bcf-fa72-46fb-bf19-3aadda914ce2",
        "name": "SB_set_user_lang",
        "credentials": {
          "supabaseApi": {
            "id": "jGgpXKPYHiL193Rz",
            "name": "Supabase account"
          }
        }
      },
      {
        "parameters": {
          "jsCode": "// n8n Code — Build items-to-delete (UNIFIED) — same chatId resolver as \"Clear ALL\"\n// Output: array of { json: { chatId, messageId } } in this order:\n//   1) sd.replyIndex[chatId] (+ keyboardMsgId if provided)\n//   2) sd.photoBucket[chatId].files[].message_id (+ targetMsgId if provided)\n//   3) keys of sd.photoIndex[chatId]\n\nconst sd = $getWorkflowStaticData('global');\n\n// Ensure unified stores (do NOT mutate here)\nsd.photoBucket = sd.photoBucket || {};   // { [chatId]: { files:[{file_id,message_id,...}], savedAt } }\nsd.photoIndex  = sd.photoIndex  || {};   // { [chatId]: { [messageId]: file_id } }\nsd.replyIndex  = sd.replyIndex  || {};   // { [chatId]: number[] }\n\n// Same chatId resolution as in your \"Clear ALL\" node\nconst first  = $input.first()?.json || {};\nconst msg    = $json.message || first.message || {};\nconst chatId = String(\n  $json.chatId ||\n  first.chatId ||\n  msg.chat?.id ||\n  $json.chat?.id ||\n  first.chat?.id ||\n  ''\n);\n\nif (!chatId) {\n  return []; // nothing to delete\n}\n\n// Optional context ids from current item\nconst keyboardMsgId = Number($json.keyboardMsgId ?? first.keyboardMsgId ?? 0);\nconst targetMsgId   = Number($json.targetMsgId   ?? first.targetMsgId   ?? 0);\n\n// Helpers\nconst isValidId = (n) => Number.isFinite(n) && n > 0;\nconst uniqOrdered = (arr) => {\n  const seen = new Set(); const out = [];\n  for (const n of arr) { const v = Number(n); if (isValidId(v) && !seen.has(v)) { seen.add(v); out.push(v); } }\n  return out;\n};\n\n// 1) inline replies (replyIndex)\nlet replyIds = Array.isArray(sd.replyIndex[chatId]) ? sd.replyIndex[chatId].slice() : [];\nif (keyboardMsgId) replyIds.push(keyboardMsgId);\nreplyIds = uniqOrdered(replyIds);\n\n// 2) photo message ids from bucket + index\nconst bucketIds = uniqOrdered((sd.photoBucket[chatId]?.files || []).map(x => x?.message_id));\nconst indexIds  = uniqOrdered(Object.keys(sd.photoIndex[chatId] || {}));\nlet photoIds = uniqOrdered([...bucketIds, ...indexIds]);\nif (targetMsgId) photoIds = uniqOrdered([...photoIds, targetMsgId]);\n\n// 3) final order: replies first, then photos\nconst all = [...replyIds, ...photoIds];\n\n// Map to items for Telegram deleteMessage\nreturn all.map(messageId => ({ json: { chatId, messageId } }));\n"
        },
        "type": "n8n-nodes-base.code",
        "typeVersion": 2,
        "position": [
          -464,
          -208
        ],
        "id": "f02cf1bb-4e6d-44bf-87f6-9f851e828d0b",
        "name": "Build items-to-delete"
      },
      {
        "parameters": {
          "jsCode": "// Delete ONE image (unified cache)\n// Inputs: chatId (string), targetMsgId (number), keyboardMsgId? (number)\n// Uses: sd.photoBucket, sd.photoIndex, sd.replyIndex\n\nconst sd = $getWorkflowStaticData('global');\nsd.photoBucket = sd.photoBucket || {};\nsd.photoIndex  = sd.photoIndex  || {};\nsd.replyIndex  = sd.replyIndex  || {};\n\nconst chatId        = String($json.chatId || '');\nconst targetMsgId   = Number($json.targetMsgId || 0);\nconst keyboardMsgId = Number($json.keyboardMsgId || 0);\n\nif (!chatId || !Number.isFinite(targetMsgId) || targetMsgId <= 0) {\n  return [{ json: { ok:false, reason:'missing_chat_or_target', chatId, targetMsgId } }];\n}\n\n// 1) убрать наш inline-reply из индекса (если есть)\nif (keyboardMsgId && Array.isArray(sd.replyIndex[chatId])) {\n  sd.replyIndex[chatId] = sd.replyIndex[chatId].filter(id => id !== keyboardMsgId);\n}\n\n// 2) найти file_id по sd.photoIndex либо по бакету\nconst bucket = sd.photoBucket[chatId];\nconst idxMap = sd.photoIndex[chatId] || {};\nlet removedFileId = idxMap[targetMsgId] || null;\n\nif (!removedFileId && bucket?.files?.length) {\n  const hit = bucket.files.find(x => Number(x?.message_id) === targetMsgId);\n  if (hit && hit.file_id) removedFileId = hit.file_id;\n}\n\n// 3) удалить из бакета по message_id (и на всякий — по совпадающему file_id)\nlet removed = false;\nif (bucket?.files?.length) {\n  const before = bucket.files.length;\n  bucket.files = bucket.files.filter(x => {\n    if (!x || typeof x !== 'object') return true;\n    if (Number(x.message_id) === targetMsgId) return false;\n    if (removedFileId && x.file_id === removedFileId) return false;\n    return true;\n  });\n  removed = bucket.files.length < before;\n  sd.photoBucket[chatId] = bucket;\n}\n\n// 4) удалить из индекса message_id→file_id (только эту запись)\nif (sd.photoIndex?.[chatId]?.[targetMsgId]) {\n  delete sd.photoIndex[chatId][targetMsgId];\n}\n\nreturn [{\n  json: {\n    ok: true,\n    chatId,\n    removed,\n    removed_file_id: removedFileId || null,\n    targetMsgId,\n    keyboardMsgId,\n    left_in_bucket: sd.photoBucket?.[chatId]?.files?.length || 0\n  }\n}];\n"
        },
        "type": "n8n-nodes-base.code",
        "typeVersion": 2,
        "position": [
          -48,
          -480
        ],
        "id": "068041d9-051b-4218-b482-b6af609ec05b",
        "name": "Delete ONE image from memory"
      },
      {
        "parameters": {
          "jsCode": "// n8n Code — Clear ALL unified caches for this chat\n// Targets ONLY the unified stores you decided to keep:\n//   sd.photoBucket[chatId]  — remove\n//   sd.photoIndex[chatId]   — remove\n//   sd.replyIndex[chatId]   — remove\n// Leaves sd.poll intact. No legacy/tool-specific caches touched.\n\nconst sd = $getWorkflowStaticData('global');\n\n// Ensure unified stores exist\nsd.photoBucket = sd.photoBucket || {};   // { [chatId]: { files:[{file_id,message_id,...}], savedAt } }\nsd.photoIndex  = sd.photoIndex  || {};   // { [chatId]: { [messageId]: file_id } }\nsd.replyIndex  = sd.replyIndex  || {};   // { [chatId]: number[] }\nsd.poll        = sd.poll        || {};\n\nconst first  = $input.first()?.json || {};\nconst msg    = $json.message || first.message || {};\nconst chatId = String(\n  $json.chatId ||\n  first.chatId ||\n  msg.chat?.id ||\n  $json.chat?.id ||\n  first.chat?.id ||\n  ''\n);\n\nlet lang = sd.userLang[chatId] || null;\n\nif (!chatId) {\n  return [{ json: { ok:false, reason:'no_chat' } }];\n}\n\n// Snapshot BEFORE\nconst before = {\n  bucket_files: sd.photoBucket?.[chatId]?.files?.length || 0,\n  index_keys:   sd.photoIndex?.[chatId] ? Object.keys(sd.photoIndex[chatId]).length : 0,\n  reply_len:    Array.isArray(sd.replyIndex?.[chatId]) ? sd.replyIndex[chatId].length : 0,\n};\n\n// (optional) collect ids we’re about to drop — handy for downstream debug/logging\nconst bucket_msg_ids = (sd.photoBucket?.[chatId]?.files || [])\n  .map(x => Number(x?.message_id))\n  .filter(n => Number.isFinite(n) && n > 0);\n\nconst index_msg_ids = sd.photoIndex?.[chatId]\n  ? Object.keys(sd.photoIndex[chatId]).map(n => Number(n)).filter(n => Number.isFinite(n) && n > 0)\n  : [];\n\nconst reply_msg_ids = Array.isArray(sd.replyIndex?.[chatId]) ? sd.replyIndex[chatId].slice() : [];\n\n// CLEAR unified caches\ndelete sd.photoBucket[chatId];\ndelete sd.photoIndex[chatId];\ndelete sd.replyIndex[chatId];\n\n// Snapshot AFTER\nconst after = {\n  bucket_files: sd.photoBucket?.[chatId]?.files?.length || 0,\n  index_keys:   sd.photoIndex?.[chatId] ? Object.keys(sd.photoIndex[chatId]).length : 0,\n  reply_len:    Array.isArray(sd.replyIndex?.[chatId]) ? sd.replyIndex[chatId].length : 0,\n};\n\nreturn [{\n  json: {\n    ok: true,\n    lang,\n    chatId,\n    before,\n    after,\n    deleted: {\n      bucket_msg_ids,\n      index_msg_ids,\n      reply_msg_ids,\n    }\n  }\n}];\n"
        },
        "type": "n8n-nodes-base.code",
        "typeVersion": 2,
        "position": [
          -240,
          -304
        ],
        "id": "0bbabd82-70ab-4f2b-b5b9-e87df2c4f8aa",
        "name": "Clear ALL buckets/indexes"
      },
      {
        "parameters": {
          "operation": "deleteMessage",
          "chatId": "={{ $json.chatId }}",
          "messageId": "={{ $json.messageId }}"
        },
        "id": "c1ae42f6-0d9f-4aed-b36e-8b10b7ad493d",
        "name": "delMes2",
        "type": "n8n-nodes-base.telegram",
        "typeVersion": 1.2,
        "position": [
          -240,
          -144
        ],
        "webhookId": "5becca08-9fa3-416f-8f0c-6e1a114ab9d6",
        "credentials": {
          "telegramApi": {
            "id": "ur7jSUPdiAaPVhCf",
            "name": "Geni AI"
          }
        },
        "onError": "continueRegularOutput"
      },
      {
        "parameters": {
          "chatId": "={{ $json.chatId }}",
          "text": "={{ $json.lang === 'en'\n  ? 'All uploaded photos have been cleared.'\n  : 'Все загруженные фото сброшены.'\n}}",
          "additionalFields": {
            "appendAttribution": false
          }
        },
        "id": "58a2d743-760c-4809-98bc-6f0cfdffc376",
        "name": "send_mes_clear",
        "type": "n8n-nodes-base.telegram",
        "typeVersion": 1.2,
        "position": [
          -48,
          -304
        ],
        "webhookId": "a46e0bb1-ac2c-43fc-b456-ef78749654ee",
        "credentials": {
          "telegramApi": {
            "id": "ur7jSUPdiAaPVhCf",
            "name": "Geni AI"
          }
        }
      },
      {
        "parameters": {
          "chatId": "={{ $('message_reply_inline').item.json.chatId }}",
          "text": "={{ $json.lang === 'en'\n    ? 'Language saved: English'\n    : 'Язык сохранён: Русский'\n  }}",
          "additionalFields": {
            "appendAttribution": false
          }
        },
        "id": "e372a0a1-50a9-4d07-a9a5-113c54b6dd3a",
        "name": "send_mes_lang_save",
        "type": "n8n-nodes-base.telegram",
        "typeVersion": 1.2,
        "position": [
          -48,
          32
        ],
        "webhookId": "6f095c52-076a-4679-baf4-721e13505ded",
        "credentials": {
          "telegramApi": {
            "id": "ur7jSUPdiAaPVhCf",
            "name": "Geni AI"
          }
        }
      },
      {
        "parameters": {
          "chatId": "={{ $json.tg_user_id }}",
          "text": "={{ $('Switch_lang_act').item.json.lang === 'en'\n    ? 'Current balance: ' + $json.balance + ' credits'\n    : 'Текущий баланс вашего аккаунта - ' + $json.balance + ' Кредитов'\n  }}",
          "additionalFields": {
            "appendAttribution": false
          }
        },
        "id": "1585199c-9601-4634-ad56-717376a72cf1",
        "name": "send_mes_balance",
        "type": "n8n-nodes-base.telegram",
        "typeVersion": 1.2,
        "position": [
          2336,
          48
        ],
        "webhookId": "92ceebf8-b19e-467b-b2ae-b534a4ea67c3",
        "credentials": {
          "telegramApi": {
            "id": "ur7jSUPdiAaPVhCf",
            "name": "Geni AI"
          }
        }
      },
      {
        "parameters": {
          "chatId": "={{ $json.chatId }}",
          "text": "={{ $json.lang === 'en'\n    ? 'Current plan: ' + ($json.plan || 'no active plan')\n    : 'Текущий статус вашего аккаунта - ' + ($json.plan || 'нет активного плана')\n  }}",
          "additionalFields": {
            "appendAttribution": false
          }
        },
        "id": "8edbccd4-7d68-417c-b90a-c65af0c90abb",
        "name": "send_mes_plan",
        "type": "n8n-nodes-base.telegram",
        "typeVersion": 1.2,
        "position": [
          2336,
          256
        ],
        "webhookId": "2a56c513-6002-466f-b3a6-4266d73a69f7",
        "credentials": {
          "telegramApi": {
            "id": "ur7jSUPdiAaPVhCf",
            "name": "Geni AI"
          }
        }
      },
      {
        "parameters": {
          "chatId": "={{ $('build_sub_invoice').item.json.chatId }}",
          "text": "={{ $('build_sub_invoice').item.json.pay_prompt_text }}",
          "replyMarkup": "inlineKeyboard",
          "inlineKeyboard": {
            "rows": [
              {
                "row": {
                  "buttons": [
                    {
                      "text": "={{ $('build_sub_invoice').item.json.ui_texts.payStars }}",
                      "additionalFields": {
                        "url": "={{ $json.result }}"
                      }
                    }
                  ]
                }
              },
              {
                "row": {
                  "buttons": [
                    {
                      "text": "={{ $('build_sub_invoice').item.json.ui_texts.payCard }}",
                      "additionalFields": {
                        "url": "={{ $('build_sub_invoice').item.json.robokassa_url }}"
                      }
                    }
                  ]
                }
              }
            ]
          },
          "additionalFields": {
            "appendAttribution": false
          }
        },
        "id": "593cdd9f-b109-4b66-ae63-359df315b547",
        "name": "send_mes_inline_buy_plan",
        "type": "n8n-nodes-base.telegram",
        "typeVersion": 1.2,
        "position": [
          2576,
          432
        ],
        "webhookId": "e2cbaf9c-00fb-4030-aa3c-6934b8c7a060",
        "credentials": {
          "telegramApi": {
            "id": "ur7jSUPdiAaPVhCf",
            "name": "Geni AI"
          }
        },
        "disabled": true
      },
      {
        "parameters": {
          "jsCode": "// n8n Code — Build \"choose credits pack\" message\n// Вход: $json с полями lang, chatId (из меню/роутера)\n// Выход: chatId, lang, text, reply_markup.inline_keyboard\n\nconst msg = $json.message || {};\nconst chatId = String(\n  $json.chatId ||\n  msg.chat?.id ||\n  $json.chat?.id ||\n  ''\n);\n\n// fallback, как в menu/аккаунте\nlet lang = (typeof $json.lang === 'string' && $json.lang.toLowerCase() === 'en') ? 'en' : 'ru';\n\n// Определяем тексты\nconst TEXT = {\n  ru: {\n    title: 'Выберите пакет кредитов:',\n    pack: (c) => `${c} Кредитов`,\n  },\n  en: {\n    title: 'Choose a credits pack:',\n    pack: (c) => `${c} credits`,\n  },\n};\n\nconst T = TEXT[lang] || TEXT.ru;\n\n// Описываем пакеты\nconst packs = [\n  { credits: 200 },\n  { credits: 500 },\n  { credits: 1000 },\n];\n\n// Строим inline_keyboard: по одному пакету в строке\nconst inline_keyboard = packs.map(p => [{\n  text: T.pack(p.credits),\n  callback_data: `credits:${p.credits}`,   // ВАЖНО: формат для Switch_inline_act\n}]);\n\nreturn [{\n  json: {\n    chatId,\n    lang,\n    text: T.title,\n    reply_markup: {\n      inline_keyboard,\n    },\n  },\n}];\n"
        },
        "type": "n8n-nodes-base.code",
        "typeVersion": 2,
        "position": [
          2032,
          640
        ],
        "id": "88c3a5a1-7380-49b0-8010-a59a26ce0c95",
        "name": "build_credits_packs"
      },
      {
        "parameters": {
          "method": "POST",
          "url": "https://api.telegram.org/bot8495842221:AAHBriXvzudyazB2f8z3ilGQ8A5ESdjjtl8/sendMessage",
          "sendBody": true,
          "bodyParameters": {
            "parameters": [
              {
                "name": "chat_id",
                "value": "={{ $json.chatId }}"
              },
              {
                "name": "text",
                "value": "={{ $json.text }}"
              },
              {
                "name": "parse_mode",
                "value": "HTML"
              },
              {
                "name": "reply_markup",
                "value": "={{ $json.reply_markup }}"
              },
              {
                "name": "disable_notification",
                "value": "=true"
              }
            ]
          },
          "options": {}
        },
        "type": "n8n-nodes-base.httpRequest",
        "typeVersion": 4.2,
        "position": [
          2336,
          640
        ],
        "id": "80906b35-746c-4a76-ab24-0f859beba4f7",
        "name": "reply_markup1",
        "retryOnFail": true
      },
      {
        "parameters": {
          "method": "POST",
          "url": "https://api.telegram.org/bot8495842221:AAHBriXvzudyazB2f8z3ilGQ8A5ESdjjtl8/createInvoiceLink",
          "sendBody": true,
          "specifyBody": "json",
          "jsonBody": "={{ $json.invoicePayload }}",
          "options": {}
        },
        "type": "n8n-nodes-base.httpRequest",
        "typeVersion": 4.2,
        "position": [
          -240,
          416
        ],
        "id": "96869bd8-cd3e-4b46-8d01-38d35024397a",
        "name": "createInvoiceLink_credits"
      },
      {
        "parameters": {
          "chatId": "={{ $('build_credits_invoice').item.json.chatId }}",
          "text": "={{ $('build_credits_invoice').item.json.ui.caption }}",
          "replyMarkup": "inlineKeyboard",
          "inlineKeyboard": {
            "rows": [
              {
                "row": {
                  "buttons": [
                    {
                      "text": "={{ $('build_credits_invoice').item.json.ui.btn_stars }}",
                      "additionalFields": {
                        "url": "={{ $json.result }}"
                      }
                    }
                  ]
                }
              },
              {
                "row": {
                  "buttons": [
                    {
                      "text": "={{ $('build_credits_invoice').item.json.ui.btn_card }}",
                      "additionalFields": {
                        "url": "={{ $('create_payment_yookassa_credits').item.json.confirmation.confirmation_url }}"
                      }
                    }
                  ]
                }
              }
            ]
          },
          "additionalFields": {
            "appendAttribution": false,
            "parse_mode": "HTML"
          }
        },
        "id": "f9ffaf79-5074-4352-aa5e-d13a34d3b6dd",
        "name": "send_mes_payment1",
        "type": "n8n-nodes-base.telegram",
        "typeVersion": 1.2,
        "position": [
          192,
          512
        ],
        "webhookId": "245ca41a-c6c4-4a94-bb1e-a918af8a165b",
        "credentials": {
          "telegramApi": {
            "id": "ur7jSUPdiAaPVhCf",
            "name": "Geni AI"
          }
        }
      },
      {
        "parameters": {
          "operation": "deleteMessage",
          "chatId": "={{ $json.chatId }}",
          "messageId": "={{ $json.keyboardMsgId }}"
        },
        "id": "50b3bb65-e6c0-46d4-b21e-fe33fae4335c",
        "name": "delMes_keyboardMsg2",
        "type": "n8n-nodes-base.telegram",
        "typeVersion": 1.2,
        "position": [
          -464,
          832
        ],
        "webhookId": "4dbc69a5-2493-4aac-baca-e4197736aca4",
        "credentials": {
          "telegramApi": {
            "id": "ur7jSUPdiAaPVhCf",
            "name": "Geni AI"
          }
        }
      },
      {
        "parameters": {
          "jsCode": "// n8n Code — payment_store_prompt\n// Храним последнее \"оплатное\" сообщение с инлайнами в staticData,\n// чтобы потом удалить его после успешной оплаты.\n\nconst sd = $getWorkflowStaticData('global');\nsd.paymentPrompt = sd.paymentPrompt || {};   // { [chatId]: message_id }\n\n// Берём первый входящий item\nconst item = $input.first()?.json || $json || {};\nconst result = item.result || {};            // ответ Telegram sendMessage\nconst chat = result.chat || item.chat || {};\n\nconst chatId = String(\n  chat.id ||\n  item.chatId ||\n  item.db_event?.chat_id ||\n  ''\n);\n\nconst messageId = result.message_id;\n\nif (chatId && messageId) {\n  sd.paymentPrompt[chatId] = messageId;\n}\n\nreturn [{\n  json: {\n    chatId,\n    messageId,\n    stored: !!(chatId && messageId),\n  }\n}];\n"
        },
        "type": "n8n-nodes-base.code",
        "typeVersion": 2,
        "position": [
          384,
          512
        ],
        "id": "39b6e6e3-f60f-475b-b8fb-3f7e33c15f98",
        "name": "payment_store_prompt"
      },
      {
        "parameters": {
          "jsCode": "// n8n Code — build_credits_invoice\n// Вход: chatId, lang, pack_credits (200/500/1000)\n// Выход: данные для Telegram Stars + данные для YooKassa (карта)\n\nconst sd = $getWorkflowStaticData('global');\nsd.userLang = sd.userLang || {};\n\nconst chatId = String($json.chatId || '');\nlet lang = (typeof $json.lang === 'string' && $json.lang.toLowerCase() === 'en') ? 'en' : 'ru';\nif (!lang && chatId && sd.userLang[chatId]) lang = sd.userLang[chatId];\n\n// выбранный пак\nconst credits = Number($json.pack_credits || 0);\nif (!Number.isFinite(credits) || credits <= 0) {\n  throw new Error('Invalid credits pack selected');\n}\n\n// Прайсинг:\n// amount_stars — цена в Stars (XTR для Telegram)\n// amount_rub   — цена в ₽ (для ЮKassa / карты)\nconst pricing = {\n  200: { amount_stars: 159, amount_rub: 199 },\n  500: { amount_stars: 389, amount_rub: 469 },\n  1000:{ amount_stars: 699, amount_rub: 899 },\n};\n\nconst baseCfg = pricing[credits] || pricing[200];\nconst amount_stars = Number(baseCfg.amount_stars) || 0;\nconst amount_rub   = Number(baseCfg.amount_rub)   || 0;\n\nif (!amount_stars || !amount_rub) {\n  throw new Error('Pricing not configured for this credits pack');\n}\n\n// Telegram Stars: минимальные единицы (как «копейки»)\n// Сейчас 1 Star = 1 единица для API (если что — потом поправим unit)\nconst unit = 1;\nconst total_units = amount_stars * unit;\n\n// Локализация текста\nconst T = {\n  ru: {\n    title:       `Пакет ${credits} Кредитов`,\n    description: `Покупка ${credits} Кредитов для аккаунта.`,\n    // Показываем и Stars, и ₽\n    pay_caption:\n      `Пакет: ${credits} Кредитов\\n` +\n      `Цена звёздами: ${amount_stars} Stars\\n` +\n      `Цена по карте: ${amount_rub} ₽`,\n    btn_stars:   '⭐ Оплатить звёздами',\n    btn_card:    '💳 Оплатить картой',\n    // Для ЮKassa\n    yk_description: `Пакет ${credits} кредитов, оплата картой.`,\n  },\n  en: {\n    title:       `${credits} Credits pack`,\n    description: `Purchase ${credits} credits for your account.`,\n    pay_caption:\n      `Pack: ${credits} credits\\n` +\n      `Price by Stars: ${amount_stars} Stars\\n` +\n      `Price by card: ${amount_rub} RUB`,\n    btn_stars:   '⭐ Pay with Stars',\n    btn_card:    '💳 Pay by card',\n    yk_description: `Pack of ${credits} credits, card payment.`,\n  },\n}[lang] || {\n  title:       `Пакет ${credits} Кредитов`,\n  description: `Покупка ${credits} Кредитов для аккаунта.`,\n  pay_caption:\n    `Пакет: ${credits} Кредитов\\n` +\n    `Цена звёздами: ${amount_stars} Stars\\n` +\n    `Цена по карте: ${amount_rub} ₽`,\n  btn_stars:   '⭐ Оплатить звёздами',\n  btn_card:    '💳 Оплатить картой',\n  yk_description: `Пакет ${credits} кредитов, оплата картой.`,\n};\n\n// payload под текущий payment.parse_success:\n// \"credits:200\", \"credits:500\", \"credits:1000\"\nconst payload = `credits:${credits}`;\n\n// === Telegram Stars invoice payload ===\nconst invoicePayload = {\n  chat_id: Number(chatId),\n  title: T.title,\n  description: T.description,\n  payload,\n  currency: 'XTR',\n  prices: [\n    { label: T.title, amount: total_units }\n  ]\n};\n\n// Пока заглушка под Робокассу (можно будет заменить на реальную ссылку)\nconst robokassa_url = 'https://example.com/robokassa/credits';\n\n// === YooKassa: подготовка данных ===\n\n// product_code — удобно использовать как \"credits_200\" / \"credits_500\" / \"credits_1000\"\nconst yk_product_code = `credits_${credits}`;\n\n// ЮKassa ждёт amount.value строкой с двумя знаками после запятой\nconst yk_amount_value = amount_rub.toFixed(2);\nconst yk_currency = 'RUB';\n\n// Описание платежа для ЮKassa\nconst yk_description = T.yk_description;\n\n// metadata — то, что вернётся в вебхуке (payment.succeeded)\nconst yk_metadata = {\n  tg_user_id: chatId,\n  credits,\n  product_code: yk_product_code,\n  source: 'telegram-bot',\n  lang,\n};\n\nreturn [{\n  json: {\n    chatId,\n    lang,\n    credits,\n\n    // Stars\n    price_stars: amount_stars,\n\n    // Рубли (для карты / ЮKassa)\n    price_rub: amount_rub,\n\n    // Telegram Stars invoice\n    invoicePayload,\n    robokassa_url,\n\n    // UI для текущих сообщений\n    ui: {\n      caption:   T.pay_caption,\n      btn_stars: T.btn_stars,\n      btn_card:  T.btn_card,\n    },\n\n    // YooKassa fields\n    yk_product_code,\n    yk_amount_value,\n    yk_currency,\n    yk_description,\n    yk_metadata,\n  }\n}];\n"
        },
        "type": "n8n-nodes-base.code",
        "typeVersion": 2,
        "position": [
          -464,
          512
        ],
        "id": "ceb2314c-fd38-45fe-9ae5-f4a989e2a37f",
        "name": "build_credits_invoice"
      },
      {
        "parameters": {
          "jsCode": "// n8n Code — payment_pick_prompt_to_delete\n// Забираем сохранённый message_id инлайна оплаты и готовим данные для deleteMessage.\n\nconst sd = $getWorkflowStaticData('global');\nsd.paymentPrompt = sd.paymentPrompt || {};   // { [chatId]: message_id }\n\n// В этом месте у нас JSON от payment.parse_success / apply_payment\nconst src = $json || {};\nconst chatId = String(\n  src.chatId ||\n  src.db_event?.chat_id ||\n  ''\n);\n\nlet delete_message_id = null;\n\nif (chatId && sd.paymentPrompt[chatId]) {\n  delete_message_id = sd.paymentPrompt[chatId];\n  // одноразово: после удаления не храним\n  delete sd.paymentPrompt[chatId];\n}\n\nreturn [{\n  json: {\n    ...src,\n    delete_chat_id: chatId || null,\n    delete_message_id: delete_message_id,\n  }\n}];\n"
        },
        "type": "n8n-nodes-base.code",
        "typeVersion": 2,
        "position": [
          -640,
          -864
        ],
        "id": "94f16d79-e687-470c-8dae-893f6db39790",
        "name": "payment_pick_prompt_to_delete"
      },
      {
        "parameters": {
          "operation": "deleteMessage",
          "chatId": "={{ $json.chatId }}",
          "messageId": "={{ $json.delete_message_id }}"
        },
        "id": "7efa892e-154f-43c1-b314-27d7710671df",
        "name": "delMes3",
        "type": "n8n-nodes-base.telegram",
        "typeVersion": 1.2,
        "position": [
          -384,
          -864
        ],
        "webhookId": "cb8798c8-d243-4bc9-bf94-f81f77c99c14",
        "credentials": {
          "telegramApi": {
            "id": "ur7jSUPdiAaPVhCf",
            "name": "Geni AI"
          }
        }
      },
      {
        "parameters": {
          "method": "POST",
          "url": "=https://bwbsclwdkighhzlyiman.supabase.co/rest/v1/rpc/wallet_grant_welcome_once",
          "authentication": "predefinedCredentialType",
          "nodeCredentialType": "supabaseApi",
          "sendHeaders": true,
          "headerParameters": {
            "parameters": [
              {
                "name": "Content-Type",
                "value": " application/json"
              }
            ]
          },
          "sendBody": true,
          "specifyBody": "json",
          "jsonBody": "={\n  \"p_tg_user_id\": \"{{ $('ensure_user').item.json.tg_user_id }}\",\n  \"p_tg_username\": \"{{ $('ensure_user').item.json.tg_username }}\",\n  \"p_amount\":      10,\n  \"p_lang\":        \"{{ $json.lang || 'ru' }}\"\n}\n",
          "options": {}
        },
        "type": "n8n-nodes-base.httpRequest",
        "typeVersion": 4.2,
        "position": [
          816,
          1248
        ],
        "id": "3e1098f2-702c-4209-84a2-d91ba85747c9",
        "name": "welcome_once",
        "retryOnFail": true,
        "credentials": {
          "supabaseApi": {
            "id": "jGgpXKPYHiL193Rz",
            "name": "Supabase account"
          }
        }
      },
      {
        "parameters": {
          "rules": {
            "values": [
              {
                "conditions": {
                  "options": {
                    "caseSensitive": true,
                    "leftValue": "",
                    "typeValidation": "strict",
                    "version": 2
                  },
                  "conditions": [
                    {
                      "id": "6337ea59-4635-406e-89d4-62505dae4f35",
                      "leftValue": "={{ $json.is_start }}",
                      "rightValue": "gpt",
                      "operator": {
                        "type": "boolean",
                        "operation": "true",
                        "singleValue": true
                      }
                    }
                  ],
                  "combinator": "and"
                }
              },
              {
                "conditions": {
                  "options": {
                    "caseSensitive": true,
                    "leftValue": "",
                    "typeValidation": "strict",
                    "version": 2
                  },
                  "conditions": [
                    {
                      "id": "d6fc6a81-7010-4119-9d91-c66d79e68ac8",
                      "leftValue": "={{ $json.is_start }}",
                      "rightValue": "=flux",
                      "operator": {
                        "type": "boolean",
                        "operation": "false",
                        "singleValue": true
                      }
                    }
                  ],
                  "combinator": "and"
                }
              }
            ]
          },
          "options": {}
        },
        "type": "n8n-nodes-base.switch",
        "typeVersion": 3.2,
        "position": [
          112,
          2016
        ],
        "id": "d5e4be4b-38b1-49ba-8644-30002f28a152",
        "name": "switch_start"
      },
      {
        "parameters": {
          "rules": {
            "values": [
              {
                "conditions": {
                  "options": {
                    "caseSensitive": true,
                    "leftValue": "",
                    "typeValidation": "strict",
                    "version": 2
                  },
                  "conditions": [
                    {
                      "id": "6337ea59-4635-406e-89d4-62505dae4f35",
                      "leftValue": "={{ $json.welcome_applied }}",
                      "rightValue": "gpt",
                      "operator": {
                        "type": "boolean",
                        "operation": "true",
                        "singleValue": true
                      }
                    }
                  ],
                  "combinator": "and"
                }
              },
              {
                "conditions": {
                  "options": {
                    "caseSensitive": true,
                    "leftValue": "",
                    "typeValidation": "strict",
                    "version": 2
                  },
                  "conditions": [
                    {
                      "id": "d6fc6a81-7010-4119-9d91-c66d79e68ac8",
                      "leftValue": "={{ $json.welcome_applied }}",
                      "rightValue": "=flux",
                      "operator": {
                        "type": "boolean",
                        "operation": "false",
                        "singleValue": true
                      }
                    }
                  ],
                  "combinator": "and"
                }
              }
            ]
          },
          "options": {}
        },
        "type": "n8n-nodes-base.switch",
        "typeVersion": 3.2,
        "position": [
          1024,
          1248
        ],
        "id": "74d71777-4e15-4326-b5be-3ea691b0b6a6",
        "name": "switch_welcome"
      },
      {
        "parameters": {
          "chatId": "={{ $('ensure_user').item.json.tg_user_id }}",
          "text": "={{ $('ensure_user').item.json.lang === 'en'\n  ? '🎁 We have credited 10 welcome credits to your account.'\n  : '🎁 Мы начислили вам 10 приветственных Кредитов.'\n}}",
          "additionalFields": {
            "appendAttribution": false
          }
        },
        "id": "765d90e2-2da4-4a68-9f89-767f2e1ea456",
        "name": "send_wel_cred",
        "type": "n8n-nodes-base.telegram",
        "typeVersion": 1.2,
        "position": [
          1232,
          1248
        ],
        "webhookId": "a2aae37a-1bcb-481d-adcf-d43a7b4b27a6",
        "credentials": {
          "telegramApi": {
            "id": "ur7jSUPdiAaPVhCf",
            "name": "Geni AI"
          }
        }
      },
      {
        "parameters": {
          "chatId": "={{ $('switch_start').item.json.chatId }}",
          "text": "={{ $('switch_start').item.json.lang === 'en'\n  ? '🫡 Welcome to GENI AI!'\n  : '🫡 Приветствую в GENI AI!'\n}}\n{{ $json.lang === 'en' ? '🌐 Choose your language:' : '🌐 Выберите язык:' }}",
          "replyMarkup": "inlineKeyboard",
          "inlineKeyboard": {
            "rows": [
              {
                "row": {
                  "buttons": [
                    {
                      "text": "Русский",
                      "additionalFields": {
                        "callback_data": "setlang:ru"
                      }
                    }
                  ]
                }
              },
              {
                "row": {
                  "buttons": [
                    {
                      "text": "English ",
                      "additionalFields": {
                        "callback_data": "setlang:en"
                      }
                    }
                  ]
                }
              }
            ]
          },
          "additionalFields": {
            "appendAttribution": false
          }
        },
        "id": "70f05f2c-cfc0-48e8-8945-9d67c3acdd03",
        "name": "send_start_mes",
        "type": "n8n-nodes-base.telegram",
        "typeVersion": 1.2,
        "position": [
          816,
          1040
        ],
        "webhookId": "1969a35d-d09b-43a2-9df5-891be378c6cf",
        "credentials": {
          "telegramApi": {
            "id": "ur7jSUPdiAaPVhCf",
            "name": "Geni AI"
          }
        }
      },
      {
        "parameters": {
          "jsCode": "// lang_to_home_after_set\n// Генерируем \"фейковое\" текстовое сообщение `/menu`,\n// чтобы прогнать его через обычный роутинг (Switch_message_type → ensure_user → menu → reply_markup).\n\nconst chatId = String($json.chatId || $input.first().json.result.chat.id);\nconst lang   = typeof $('SB_set_user_lang').first().json.lang === 'string' ? $('SB_set_user_lang').first().json.lang.toLowerCase() : null;\n\nif (!chatId) {\n  // На всякий случай, если вдруг что-то пошло не так — просто ничего не делаем\n  return [];\n}\n\nconst now = Math.floor(Date.now() / 1000);\n\nreturn [{\n  json: {\n    // Эмулируем update.message, как будто Telegram прислал /menu\n    message: {\n      message_id: 0,              // фиктивный id — он нужен только для совместимости\n      from: {\n        id: Number(chatId) || chatId,\n        is_bot: false,\n        language_code: lang || 'ru',\n      },\n      chat: {\n        id: Number(chatId) || chatId,\n        type: 'private',\n      },\n      date: now,\n      text: '/menu',\n      entities: [\n        {\n          offset: 0,\n          length: 5,\n          type: 'bot_command',\n        },\n      ],\n    },\n    // Дополнительно можно пробросить lang, но ensure_user уже вернёт актуальный\n    lang: lang || undefined,\n  },\n}];\n"
        },
        "type": "n8n-nodes-base.code",
        "typeVersion": 2,
        "position": [
          160,
          32
        ],
        "id": "5b35d56b-e756-40f6-b8fa-175fea97c464",
        "name": "lang_to_home_after_set"
      },
      {
        "parameters": {
          "operation": "deleteMessage",
          "chatId": "={{ $json.result.chat.id }}",
          "messageId": "={{ $('Switch1').item.json.deleteMessageId }}"
        },
        "id": "bbfccd08-7ba1-426e-ab8f-331276322a8c",
        "name": "delMes4",
        "type": "n8n-nodes-base.telegram",
        "typeVersion": 1.2,
        "position": [
          2224,
          1264
        ],
        "webhookId": "5af3ec5f-9501-4891-bb28-8d52e321d7cc",
        "credentials": {
          "telegramApi": {
            "id": "ur7jSUPdiAaPVhCf",
            "name": "Geni AI"
          }
        },
        "onError": "continueRegularOutput"
      },
      {
        "parameters": {
          "jsCode": "// lang_to_home_after_set\n// Генерируем \"фейковое\" текстовое сообщение `/menu`,\n// чтобы прогнать его через обычный роутинг (Switch_message_type → ensure_user → menu → \nconst chatId = String($('Switch1').first().json.chatId);\nconst lang   = typeof $('Switch1').first().json.lang === 'string' ? $('Switch1').first().json.lang.toLowerCase() : null;\n\nif (!chatId) {\n  // На всякий случай, если вдруг что-то пошло не так — просто ничего не делаем\n  return [];\n}\n\nconst now = Math.floor(Date.now() / 1000);\n\nreturn [{\n  json: {\n    // Эмулируем update.message, как будто Telegram прислал /menu\n    message: {\n      message_id: 0,              // фиктивный id — он нужен только для совместимости\n      from: {\n        id: Number(chatId) || chatId,\n        is_bot: false,\n        language_code: lang || 'ru',\n      },\n      chat: {\n        id: Number(chatId) || chatId,\n        type: 'private',\n      },\n      date: now,\n      text: '/menu',\n      entities: [\n        {\n          offset: 0,\n          length: 5,\n          type: 'bot_command',\n        },\n      ],\n    },\n    // Дополнительно можно пробросить lang, но ensure_user уже вернёт актуальный\n    lang: lang || undefined,\n  },\n}];\n"
        },
        "type": "n8n-nodes-base.code",
        "typeVersion": 2,
        "position": [
          2464,
          1264
        ],
        "id": "98ba3ef7-444e-46e9-b1f2-73d9d293c3bf",
        "name": "lang_to_home_after_set1"
      },
      {
        "parameters": {
          "chatId": "={{ $json.chatId }}",
          "text": "={{ $json.lang === 'en'\n  ? '📘 How to use Geni AI\\n\\n' +\n    '1. Open the menu and choose 🖼 Image → ⚡️ FLUX.\\n' +\n    '2. Send a text prompt (and optionally a reference photo).\\n' +\n    '3. The bot will generate images and spend credits for each request.\\n\\n' +\n    '💰 Balance & credits:\\n' +\n    '- Use 👤 Account → 💰 Balance to see your credits.\\n' +\n    '- Use 👤 Account → ⚡️ Buy credits to top up.\\n\\n' +\n    'If you run out of credits, the bot will show how to buy more.\\n' +\n    'If you need help text to me @GRIGORIY_VOYAKIN.'\n  : '📘 Как пользоваться Geni AI\\n\\n' +\n    '1. Откройте меню и выберите 🖼 Image → ⚡️ FLUX.\\n' +\n    '2. Отправьте текстовый промпт (можно добавить референс-фото).\\n' +\n    '3. Бот сгенерирует изображения и спишет Кредиты за запрос.\\n\\n' +\n    '💰 Баланс и Кредиты:\\n' +\n    '- В разделе 👤 Аккаунт → 💰 Баланс можно посмотреть остаток.\\n' +\n    '- В 👤 Аккаунт → ⚡️ Купить кредиты пополнить счёт.\\n\\n' +\n    'Если Кредитов не хватит, бот покажет, как их докупить.\\n' +\n    'Если нужна помощь, напишите мне @GRIGORIY_VOYAKIN.'\n}}",
          "additionalFields": {
            "appendAttribution": false,
            "parse_mode": "HTML"
          }
        },
        "id": "80a83edf-7382-4d9d-8c31-f491521d772b",
        "name": "Instruct_mes",
        "type": "n8n-nodes-base.telegram",
        "typeVersion": 1.2,
        "position": [
          2016,
          1264
        ],
        "webhookId": "c063a8aa-5e3d-4f7f-9969-213d4a97cb49",
        "credentials": {
          "telegramApi": {
            "id": "ur7jSUPdiAaPVhCf",
            "name": "Geni AI"
          }
        }
      },
      {
        "parameters": {
          "rules": {
            "values": [
              {
                "conditions": {
                  "options": {
                    "caseSensitive": true,
                    "leftValue": "",
                    "typeValidation": "strict",
                    "version": 2
                  },
                  "conditions": [
                    {
                      "id": "6337ea59-4635-406e-89d4-62505dae4f35",
                      "leftValue": "={{ $('switch_reply_menu').item.json.homeInlineShow }}",
                      "rightValue": "gpt",
                      "operator": {
                        "type": "boolean",
                        "operation": "true",
                        "singleValue": true
                      }
                    }
                  ],
                  "combinator": "and"
                }
              },
              {
                "conditions": {
                  "options": {
                    "caseSensitive": true,
                    "leftValue": "",
                    "typeValidation": "strict",
                    "version": 2
                  },
                  "conditions": [
                    {
                      "id": "d6fc6a81-7010-4119-9d91-c66d79e68ac8",
                      "leftValue": "={{ $('switch_reply_menu').item.json.homeInlineShow }}",
                      "rightValue": "=flux",
                      "operator": {
                        "type": "boolean",
                        "operation": "false",
                        "singleValue": true
                      }
                    }
                  ],
                  "combinator": "and"
                }
              }
            ]
          },
          "options": {}
        },
        "type": "n8n-nodes-base.switch",
        "typeVersion": 3.2,
        "position": [
          624,
          1568
        ],
        "id": "450f61d7-17f9-4f05-be64-cadd2feaecf7",
        "name": "switch_home_inline",
        "disabled": true
      },
      {
        "parameters": {
          "method": "POST",
          "url": "https://api.telegram.org/bot8495842221:AAHBriXvzudyazB2f8z3ilGQ8A5ESdjjtl8/sendMessage",
          "sendBody": true,
          "bodyParameters": {
            "parameters": [
              {
                "name": "chat_id",
                "value": "={{ $('switch_reply_menu').item.json.chatId }}"
              },
              {
                "name": "text",
                "value": "={{ $('switch_reply_menu').item.json.homeInlineText }}"
              },
              {
                "name": "parse_mode",
                "value": "HTML"
              },
              {
                "name": "reply_markup",
                "value": "={{ $('switch_reply_menu').item.json.homeInlineMarkup }}"
              },
              {
                "name": "disable_notification",
                "value": "={{ $('switch_reply_menu').item.json.homeInlineShow }}"
              }
            ]
          },
          "options": {}
        },
        "type": "n8n-nodes-base.httpRequest",
        "typeVersion": 4.2,
        "position": [
          816,
          1568
        ],
        "id": "8133c5ce-d354-46fd-b97f-ee56e2787aa7",
        "name": "send_home_inline",
        "retryOnFail": true,
        "disabled": true
      },
      {
        "parameters": {
          "mode": "combine",
          "combineBy": "combineByPosition",
          "options": {}
        },
        "type": "n8n-nodes-base.merge",
        "typeVersion": 3.2,
        "position": [
          -16,
          512
        ],
        "id": "8d564289-84e6-4048-9815-87c5901d4763",
        "name": "Merge"
      },
      {
        "parameters": {
          "method": "POST",
          "url": "=https://bwbsclwdkighhzlyiman.supabase.co/rest/v1/payment_messages",
          "authentication": "predefinedCredentialType",
          "nodeCredentialType": "supabaseApi",
          "sendHeaders": true,
          "headerParameters": {
            "parameters": [
              {
                "name": "Content-Type",
                "value": " application/json"
              }
            ]
          },
          "sendBody": true,
          "specifyBody": "json",
          "jsonBody": "={\n  \"provider\": \"yookassa\",\n  \"charge_id\": \"{{ $('create_payment_yookassa_credits').item.json.id }}\",\n  \"chat_id\": {{ $json.result.chat.id }},\n  \"message_id\": {{ $json.result.message_id }}\n}",
          "options": {}
        },
        "type": "n8n-nodes-base.httpRequest",
        "typeVersion": 4.2,
        "position": [
          384,
          720
        ],
        "id": "d8aa30d1-3f09-4ebe-8189-acff9d636cbe",
        "name": "store_payment_message",
        "retryOnFail": true,
        "credentials": {
          "supabaseApi": {
            "id": "jGgpXKPYHiL193Rz",
            "name": "Supabase account"
          }
        }
      },
      {
        "parameters": {
          "method": "POST",
          "url": "https://api.yookassa.ru/v3/payments",
          "authentication": "genericCredentialType",
          "genericAuthType": "httpBasicAuth",
          "sendHeaders": true,
          "headerParameters": {
            "parameters": [
              {
                "name": "Idempotence-Key",
                "value": "={{ $json.chatId + ':' + $json.credits + ':' + Date.now() }}"
              },
              {
                "name": "Content-Type",
                "value": "application/json"
              }
            ]
          },
          "sendBody": true,
          "specifyBody": "json",
          "jsonBody": "={\n  \"amount\": {\n    \"value\": \"{{ $json.price_rub }}\",\n    \"currency\": \"RUB\"\n  },\n  \"capture\": true,\n  \"confirmation\": {\n    \"type\": \"redirect\",\n    \"return_url\": \"https://t.me/Geni_AI_bot\"\n  },\n  \"description\": \"{{ `Пакет ${$json.credits} кредитов. TG: ${$json.chatId}` }}\",\n  \"metadata\": {\n    \"tg_chat_id\": \"{{ $json.chatId }}\",\n    \"product\": \"credits\",\n    \"credits\": \"{{ $json.credits }}\",\n    \"lang\": \"{{ $json.yk_metadata.lang }}\"\n  },\n  \"receipt\": {\n    \"customer\": {\n      \"email\": \"grigoriyvoyakinwork@gmail.com\"\n    },\n    \"items\": [\n      {\n        \"description\": \"{{ `Пакет ${$json.credits} кредитов` }}\",\n        \"quantity\": 1,\n        \"amount\": {\n          \"value\": \"{{ $json.price_rub }}\",\n          \"currency\": \"RUB\"\n        },\n        \"vat_code\": 1\n      }\n    ]\n  }\n}\n",
          "options": {}
        },
        "type": "n8n-nodes-base.httpRequest",
        "typeVersion": 4.2,
        "position": [
          -240,
          640
        ],
        "id": "200b1c87-9d9b-454c-97a4-043c941bfd85",
        "name": "create_payment_yookassa_credits",
        "credentials": {
          "httpBasicAuth": {
            "id": "eoZ95FIg2ZXlecAm",
            "name": "yookassa_prod"
          }
        }
      },
      {
        "parameters": {
          "method": "POST",
          "url": "=https://bwbsclwdkighhzlyiman.supabase.co/rest/v1/rpc/get_user_id_and_balance",
          "authentication": "predefinedCredentialType",
          "nodeCredentialType": "supabaseApi",
          "sendHeaders": true,
          "headerParameters": {
            "parameters": [
              {
                "name": "Content-Type",
                "value": " application/json"
              }
            ]
          },
          "sendBody": true,
          "specifyBody": "json",
          "jsonBody": "={\n  \"p_tg_user_id\": \"{{ $json.chatId }}\"\n}\n",
          "options": {}
        },
        "type": "n8n-nodes-base.httpRequest",
        "typeVersion": 4.2,
        "position": [
          2032,
          48
        ],
        "id": "f30d1b6f-58db-44e1-bb7f-1ea561dc249b",
        "name": "id_and_balance",
        "retryOnFail": true,
        "credentials": {
          "supabaseApi": {
            "id": "jGgpXKPYHiL193Rz",
            "name": "Supabase account"
          }
        }
      },
      {
        "parameters": {
          "method": "POST",
          "url": "=https://bwbsclwdkighhzlyiman.supabase.co/rest/v1/rpc/ensure_user",
          "authentication": "predefinedCredentialType",
          "nodeCredentialType": "supabaseApi",
          "sendHeaders": true,
          "headerParameters": {
            "parameters": [
              {
                "name": "Content-Type",
                "value": " application/json"
              }
            ]
          },
          "sendBody": true,
          "specifyBody": "json",
          "jsonBody": "={\n  \"p_tg_user_id\": \"{{ $json.chatId }}\",\n  \"p_tg_username\": \"{{ $('Telegram Trigger1').item.json.message?.from?.username || '' }}\"\n}\n",
          "options": {}
        },
        "type": "n8n-nodes-base.httpRequest",
        "typeVersion": 4.2,
        "position": [
          624,
          1184
        ],
        "id": "ff277c5c-10bc-4522-a6f7-63b675ba9e58",
        "name": "ensure_user",
        "retryOnFail": true,
        "credentials": {
          "supabaseApi": {
            "id": "jGgpXKPYHiL193Rz",
            "name": "Supabase account"
          }
        }
      },
      {
        "parameters": {
          "rules": {
            "values": [
              {
                "conditions": {
                  "options": {
                    "caseSensitive": true,
                    "leftValue": "",
                    "typeValidation": "strict",
                    "version": 2
                  },
                  "conditions": [
                    {
                      "id": "6337ea59-4635-406e-89d4-62505dae4f35",
                      "leftValue": "={{ $json.action }}",
                      "rightValue": "STORED",
                      "operator": {
                        "type": "string",
                        "operation": "equals"
                      }
                    }
                  ],
                  "combinator": "and"
                }
              },
              {
                "conditions": {
                  "options": {
                    "caseSensitive": true,
                    "leftValue": "",
                    "typeValidation": "strict",
                    "version": 2
                  },
                  "conditions": [
                    {
                      "id": "d6fc6a81-7010-4119-9d91-c66d79e68ac8",
                      "leftValue": "={{ $json.action }}",
                      "rightValue": "=GENERATE",
                      "operator": {
                        "type": "string",
                        "operation": "equals"
                      }
                    }
                  ],
                  "combinator": "and"
                }
              },
              {
                "conditions": {
                  "options": {
                    "caseSensitive": true,
                    "leftValue": "",
                    "typeValidation": "strict",
                    "version": 2
                  },
                  "conditions": [
                    {
                      "id": "9cff2c53-bb3e-49bf-9cfe-53963eac8978",
                      "leftValue": "={{ $json.action }}",
                      "rightValue": "CLEARED",
                      "operator": {
                        "type": "string",
                        "operation": "equals",
                        "name": "filter.operator.equals"
                      }
                    }
                  ],
                  "combinator": "and"
                }
              },
              {
                "conditions": {
                  "options": {
                    "caseSensitive": true,
                    "leftValue": "",
                    "typeValidation": "strict",
                    "version": 2
                  },
                  "conditions": [
                    {
                      "id": "39ccebaf-cc95-4d0b-954f-71fab6178691",
                      "leftValue": "={{ $json.action }}",
                      "rightValue": "NOOP ",
                      "operator": {
                        "type": "string",
                        "operation": "equals",
                        "name": "filter.operator.equals"
                      }
                    }
                  ],
                  "combinator": "and"
                }
              },
              {
                "conditions": {
                  "options": {
                    "caseSensitive": true,
                    "leftValue": "",
                    "typeValidation": "strict",
                    "version": 2
                  },
                  "conditions": [
                    {
                      "id": "a9f7ce9b-cd18-4138-83f1-8cb70c4643ed",
                      "leftValue": "={{ $json.action }}",
                      "rightValue": "REMIND",
                      "operator": {
                        "type": "string",
                        "operation": "equals",
                        "name": "filter.operator.equals"
                      }
                    }
                  ],
                  "combinator": "and"
                }
              }
            ]
          },
          "options": {}
        },
        "type": "n8n-nodes-base.switch",
        "typeVersion": 3.2,
        "position": [
          2032,
          2224
        ],
        "id": "d13fd2f7-d37a-42d1-b4bb-b2748182316c",
        "name": "Switch2"
      },
      {
        "parameters": {
          "chatId": "={{ $json.chatId }}",
          "text": "={{ $json.lang === 'en'\n  ? 'Send a photo or a prompt for generation.'\n  : 'Пришлите фото или промпт для генерации.'\n}}",
          "additionalFields": {
            "appendAttribution": false
          }
        },
        "id": "06fdf684-8a54-40e4-b5b3-d2c2e1d2af36",
        "name": "Telegram18",
        "type": "n8n-nodes-base.telegram",
        "typeVersion": 1.2,
        "position": [
          2304,
          2704
        ],
        "webhookId": "192bb390-c119-4d8f-b8cc-bbfe51bc0372",
        "credentials": {
          "telegramApi": {
            "id": "ur7jSUPdiAaPVhCf",
            "name": "Geni AI"
          }
        }
      },
      {
        "parameters": {
          "chatId": "={{ $json.chatId }}",
          "text": "={{ $json.lang === 'en'\n  ? 'All uploaded photos have been cleared.'\n  : 'Все загруженные фото сброшены.'\n}}",
          "additionalFields": {
            "appendAttribution": false
          }
        },
        "id": "deea1c46-34cc-4516-8998-197702f9353f",
        "name": "Telegram19",
        "type": "n8n-nodes-base.telegram",
        "typeVersion": 1.2,
        "position": [
          2304,
          2512
        ],
        "webhookId": "3cea3fd1-dd4f-4d78-9b0e-200130d147c1",
        "credentials": {
          "telegramApi": {
            "id": "ur7jSUPdiAaPVhCf",
            "name": "Geni AI"
          }
        }
      },
      {
        "parameters": {
          "chatId": "={{ $json.chatId }}",
          "text": "={{ $json.lang === 'en'\n  ? `${$json.reply_text}\\n⚡ Generation cost: ${$json.price} credits.\\nTap a button to delete.`\n  : `${$json.reply_text}\\n⚡Стоимость генерации: ${$json.price} кредитов.\\nНажмите кнопку, чтобы удалить.`\n}}",
          "replyMarkup": "inlineKeyboard",
          "inlineKeyboard": {
            "rows": [
              {
                "row": {
                  "buttons": [
                    {
                      "text": "={{ $json.lang === 'en'\n  ? '🗑 Delete this photo'\n  : '🗑 Удалить это фото'\n}}",
                      "additionalFields": {
                        "callback_data": "del:${$json.photo_message_id}"
                      }
                    }
                  ]
                }
              },
              {
                "row": {
                  "buttons": [
                    {
                      "text": "={{ $json.lang === 'en'\n  ? '🧺 Clear all'\n  : '🧺 Очистить все'\n}}",
                      "additionalFields": {
                        "callback_data": "del_all"
                      }
                    }
                  ]
                }
              }
            ]
          },
          "additionalFields": {
            "appendAttribution": false,
            "reply_to_message_id": "={{ $json.reply_to_message_id }}"
          }
        },
        "id": "72a12cdd-2b6e-41a4-967f-a1799ccbdfe3",
        "name": "Telegram20",
        "type": "n8n-nodes-base.telegram",
        "typeVersion": 1.2,
        "position": [
          2480,
          2080
        ],
        "webhookId": "6969c97f-2c91-44e1-ae46-426d11c99897",
        "credentials": {
          "telegramApi": {
            "id": "ur7jSUPdiAaPVhCf",
            "name": "Geni AI"
          }
        }
      },
      {
        "parameters": {
          "jsCode": "const sd = $getWorkflowStaticData('global');\nsd.photoIndex  = sd.photoIndex  || {}; // { [chatId]: { [messageId]: file_id } }\n\nconst chatId = String($json.chatId || '');\nconst msgId  = Number($json.photo_message_id ?? $json.deleteMessageId ?? 0);\nconst fileId = $json.file_id || $json.photo_file_id || $json.primary_file?.file_id || null;\n\n\nif (chatId && msgId && fileId) {\n  sd.photoIndex[chatId] = sd.photoIndex[chatId] || {};\n  sd.photoIndex[chatId][msgId] = fileId;\n}\n\nreturn [{ json: $json }];\n"
        },
        "type": "n8n-nodes-base.code",
        "typeVersion": 2,
        "position": [
          2304,
          2080
        ],
        "id": "6a4b738a-17f0-40fb-b913-75b0cc157b72",
        "name": "Code15"
      },
      {
        "parameters": {
          "jsCode": "const sd = $getWorkflowStaticData('global');\nsd.replyIndex = sd.replyIndex || {}; // { [chatId]: number[] }\n\nconst chatId = String($json.chatId || $json.result?.chat?.id || '');\nconst sentId =\n  Number($json.result?.message_id) ||\n  Number($json.result?.message?.message_id) ||\n  Number($json.message_id) || 0;\n\nif (chatId && sentId) {\n  sd.replyIndex[chatId] = sd.replyIndex[chatId] || [];\n  if (!sd.replyIndex[chatId].includes(sentId)) sd.replyIndex[chatId].push(sentId);\n}\n\nreturn [{ json: $json }];\n"
        },
        "type": "n8n-nodes-base.code",
        "typeVersion": 2,
        "position": [
          2656,
          2080
        ],
        "id": "a0a675a4-7296-4e10-b9fe-cdbba0206956",
        "name": "Code23"
      },
      {
        "parameters": {
          "jsCode": "// FLUX controller (t2i/i2i) — unified cache only\n// Outputs: action, chatId, reply_text, settings_text, reply_to_message_id,\n//          prompt, photo_file_id, ar, useRef, ref, refInterp, seed, quality, lang, balance\n\nconst sd = $getWorkflowStaticData('global');\nsd.flux        = sd.flux        || {};   // { [chatId]: { ar, ref, seed, quality, useRef } }\nsd.photoBucket = sd.photoBucket || {};   // { [chatId]: { files:[{file_id, message_id, ...}], savedAt } }\nsd.userLang    = sd.userLang    || {};\n\nconst msg    = $json.message || {};\nconst chatId = String($json.chatId || msg.chat?.id || $json.chat?.id || '');\nconst lang   = (typeof $json.lang === 'string' && /^(ru|en)$/i.test($json.lang))\n  ? $json.lang.toLowerCase()\n  : (sd.userLang[chatId] || (/^en/i.test(String(msg.from?.language_code||'')) ? 'en' : 'ru'));\nsd.userLang[chatId] = lang;\n\nconst balance = Number.isFinite(Number($json.balance)) ? Number($json.balance) : null;\nconst emit = (payload) => [{ json: { ...payload, lang, balance } }];\n\nconst promptNorm = typeof $json.prompt === 'string' ? $json.prompt : '';\nconst tgText     = typeof msg.text === 'string' ? msg.text : (typeof msg.caption === 'string' ? msg.caption : '');\nconst prompt     = (promptNorm || tgText || '').trim();\nconst hasText    = prompt.length > 0;\nconst reply_to_message_id = (msg.message_id ?? $json.deleteMessageId ?? null);\n\n// I18N\nconst I18N = {\n  ru: {\n    stored:   (n) => `📥 Фото сохранено (${n}). Отправьте текст — запущу i2i.`,\n    remind:   `Пришлите текст — запущу t2i, или фото затем текст — запущу i2i.`,\n    noop:     `Отправьте текст для запуска.`,\n    settings: (s) => [\n      `🧩 FLUX (auto t2i/i2i)`,\n      `📐 AR: ${s.ar} · ${s.dimText}`,\n      `🎛 Ref: ${s.useRef ? s.ref.toFixed(2) : 'off'} (interp: ${s.refInterp.toFixed(2)})`,\n      `🌱 Seed: ${s.seed === 0 ? 'random' : s.seed}`,\n      `⚙️ Quality: ${s.quality === 0 ? 'fast' : 'high'}`\n    ].join('\\n'),\n  },\n  en: {\n    stored:   (n) => `📥 Photo saved (${n}). Send text to run i2i.`,\n    remind:   `Send text to run t2i, or photo then text to run i2i.`,\n    noop:     `Send a text prompt to start.`,\n    settings: (s) => [\n      `🧩 FLUX (auto t2i/i2i)`,\n      `📐 AR: ${s.ar} · ${s.dimText}`,\n      `🎛 Ref: ${s.useRef ? s.ref.toFixed(2) : 'off'} (interp: ${s.refInterp.toFixed(2)})`,\n      `🌱 Seed: ${s.seed === 0 ? 'random' : s.seed}`,\n      `⚙️ Quality: ${s.quality === 0 ? 'fast' : 'high'}`\n    ].join('\\n'),\n  }\n};\nconst T = I18N[lang] || I18N.ru;\n\n// Flux settings\nconst defaults = { ar: '1:1', ref: 0.75, seed: 0, quality: 1, useRef: false };\nconst stored = (sd.flux[chatId] && typeof sd.flux[chatId] === 'object') ? sd.flux[chatId] : defaults;\n\nlet seedRaw = Number(stored.seed ?? 0);\nif (!Number.isFinite(seedRaw)) seedRaw = 0;\nconst seed = (seedRaw === 0) ? Math.floor(Math.random() * Number.MAX_SAFE_INTEGER) : seedRaw;\n\nlet ref = Number(stored.ref ?? defaults.ref);\nif (!Number.isFinite(ref)) ref = defaults.ref;\nref = Math.max(0, Math.min(1.2, ref));\nconst refInterp = (() => {\n  if (!Number.isFinite(ref) || ref <= 0) return 0;\n  const t = Math.min(Math.max(ref / 1.2, 0), 1);\n  return 5 + (1 - 5) * t;\n})();\n\nlet qRaw = Number(stored.quality ?? defaults.quality);\nconst quality = Number.isFinite(qRaw) ? qRaw : 1;\n\nfunction arToDims(ar) {\n  const [w, h] = String(ar || '1:1').split(':').map(n => parseInt(n,10) || 1);\n  const base = 1024;\n  const W = (w >= h) ? base : Math.round(base * w / h);\n  const H = (w >= h) ? Math.round(base * h / w) : base;\n  return `${W}×${H}`;\n}\nconst ar = (typeof stored.ar === 'string' && /^\\d+:\\d+$/.test(stored.ar)) ? stored.ar : defaults.ar;\nconst dimText = arToDims(ar);\n\n// detect incoming image\nlet incoming = null;\nif ($json.photo_file_id) {\n  incoming = {\n    file_id: $json.photo_file_id,\n    kind: $json.file_kind || 'photo',\n    file_name: $json.file_name || null,\n    mime_type: $json.mime_type || null,\n    message_id: (msg.message_id ?? $json.deleteMessageId ?? null),\n    savedAt: new Date().toISOString()\n  };\n}\nif (!incoming && msg.document?.file_id) {\n  const mt   = String(msg.document.mime_type || '').toLowerCase();\n  const name = String(msg.document.file_name || '');\n  const looksImage = mt.startsWith('image/') || /\\.(png|jpe?g|webp|bmp|gif|tiff?)$/i.test(name);\n  if (looksImage) {\n    incoming = {\n      file_id: msg.document.file_id,\n      kind: 'document',\n      file_name: name || null,\n      mime_type: msg.document.mime_type || null,\n      message_id: (msg.message_id ?? $json.deleteMessageId ?? null),\n      savedAt: new Date().toISOString()\n    };\n  }\n}\nif (!incoming && Array.isArray(msg.photo) && msg.photo.length) {\n  const best = msg.photo[msg.photo.length - 1];\n  incoming = {\n    file_id: best.file_id,\n    kind: 'photo',\n    file_name: null,\n    mime_type: null,\n    message_id: (msg.message_id ?? $json.deleteMessageId ?? null),\n    savedAt: new Date().toISOString()\n  };\n}\n\n// helpers\nfunction ensureBucket() {\n  return sd.photoBucket[chatId] ?? { files: [], savedAt: new Date().toISOString() };\n}\nfunction pushLimited(b, f, limit=4) {\n  const exists = (b.files || []).some(x => x.file_id === f.file_id);\n  if (!exists) {\n    (b.files = b.files || []).push(f);\n    if (b.files.length > limit) b.files = b.files.slice(-limit);\n  }\n  return b;\n}\n\n// guards\nif (!chatId) {\n  const settings_text = T.settings({ ar, dimText, useRef:false, ref:0, refInterp:0, seed, quality });\n  return emit({\n    action: 'REMIND', chatId: '',\n    reply_text: 'no chat', settings_text, reply_to_message_id: reply_to_message_id ?? null,\n    prompt:'', photo_file_id:null, ar, useRef:false, ref:0, refInterp:0, seed, quality\n  });\n}\n\n// /clear → очистка бакета\nif (/^\\/clear\\b/i.test(prompt)) {\n  if (sd.photoBucket[chatId]?.files) sd.photoBucket[chatId].files = [];\n  const settings_text = T.settings({ ar, dimText, useRef:false, ref, refInterp:0, seed, quality });\n  return emit({\n    action: 'CLEARED',\n    chatId,\n    reply_text: lang==='en' ? '🗑️ Reference bucket cleared.' : '🗑️ Копилка референсов очищена.',\n    settings_text,\n    reply_to_message_id,\n    prompt: '',\n    photo_file_id: null,\n    ar, useRef:false, ref:0, refInterp:0, seed, quality\n  });\n}\n\n// flow\nconst bucket = sd.photoBucket[chatId] || { files: [] };\nconst files  = bucket.files || [];\nconst hasStoredPhoto = files.length > 0;\n\n// 1) Фото без текста → складируем\nif (incoming && !hasText) {\n  const b = pushLimited(ensureBucket(), incoming, 4);\n  sd.photoBucket[chatId] = b;\n  const settings_text = T.settings({ ar, dimText, useRef:true, ref, refInterp, seed, quality });\n  return emit({\n    action: 'STORED',\n    chatId,\n    reply_text: T.stored(b.files.length),\n    settings_text,\n    reply_to_message_id,\n    prompt: '',\n    photo_file_id: null,\n    ar, useRef:true, ref, refInterp, seed, quality\n  });\n}\n\n// 2) t2i\nif (hasText && !hasStoredPhoto && !incoming) {\n  const settings_text = T.settings({ ar, dimText, useRef:false, ref:0, refInterp:0, seed, quality });\n  return emit({\n    action: 'GENERATE',\n    chatId,\n    reply_text: '',\n    settings_text,\n    reply_to_message_id,\n    prompt,\n    photo_file_id: null,\n    ar, useRef:false, ref:0, refInterp:0, seed, quality\n  });\n}\n\n// 3) i2i\nif (hasText && (hasStoredPhoto || incoming)) {\n  const all = hasStoredPhoto ? files.slice() : [];\n  if (incoming) all.push(incoming);\n  const primary = all[all.length - 1];\n\n  const settings_text = T.settings({ ar, dimText, useRef:true, ref, refInterp, seed, quality });\n  sd.photoBucket[chatId] = { files: all.slice(-4), savedAt: (bucket.savedAt || new Date().toISOString()) };\n\n  return emit({\n    action: 'GENERATE',\n    chatId,\n    reply_text: '',\n    settings_text,\n    reply_to_message_id,\n    prompt,\n    photo_file_id: primary?.file_id || null,\n    ar, useRef:true, ref, refInterp, seed, quality\n  });\n}\n\n// 4) подсказки/фолбек\nif (!hasText && !incoming && !hasStoredPhoto) {\n  const settings_text = T.settings({ ar, dimText, useRef:false, ref:0, refInterp:0, seed, quality });\n  return emit({\n    action: 'REMIND',\n    chatId,\n    reply_text: T.remind,\n    settings_text,\n    reply_to_message_id,\n    prompt: '',\n    photo_file_id: null,\n    ar, useRef:false, ref:0, refInterp:0, seed, quality\n  });\n}\n\nconst settings_text = T.settings({ ar, dimText, useRef:true, ref, refInterp, seed, quality });\nreturn emit({\n  action: 'NOOP',\n  chatId,\n  reply_text: T.noop,\n  settings_text,\n  reply_to_message_id,\n  prompt: '',\n  photo_file_id: null,\n  ar, useRef:true, ref, refInterp, seed, quality\n});\n"
        },
        "type": "n8n-nodes-base.code",
        "typeVersion": 2,
        "position": [
          1664,
          2272
        ],
        "id": "3e318ac2-354b-40dd-a744-cc1318b54142",
        "name": "Flux settings"
      },
      {
        "parameters": {
          "jsCode": "// n8n Code node — Clear caches after generation (UNIFIED FORMAT)\n// Берёт chatId из ответа Telegram sendPhoto / sendDocument:\n//   { ok: true, result: { chat: { id: ... }, ... } }\n//\n// ЧТО ДЕЛАЕТ:\n//   - sd.photoBucket[chatId]     (CLEARED)\n//   - sd.replyIndex[chatId]      (CLEARED)\n//   - sd.photoIndex[chatId]      (PRUNE по message_id; сама map остаётся)\n// $json.deleted_message_ids — по-прежнему опционален.\n\nconst sd = $getWorkflowStaticData('global');\n\n// Ensure unified caches exist\nsd.photoBucket = sd.photoBucket || {};     // { [chatId]: { files:[{file_id, message_id, ...}], savedAt } }\nsd.photoIndex  = sd.photoIndex  || {};     // { [chatId]: { [messageId]: file_id } }\nsd.replyIndex  = sd.replyIndex  || {};     // { [chatId]: number[] }\nsd.poll        = sd.poll        || {};     // left intact\n\n// ---- Определяем chatId из входящего item (ответ Telegram)\nconst item = $input.first()?.json || $json || {};\n\nconst chatId = $('Switch2').first().json.chatId;\n\n// Если вдруг чата нет — ничего не трогаем, но ok=true, чтобы пайплайн не падал\nif (!chatId) {\n  return [{\n    json: {\n      ok: true,\n      chatId: null,\n      skipped: 'no_chatId_in_input'\n    }\n  }];\n}\n\n// BEFORE snapshot\nconst before = {\n  photoBucket_files: sd.photoBucket?.[chatId]?.files?.length || 0,\n  photoIndex_keys:   sd.photoIndex?.[chatId] ? Object.keys(sd.photoIndex[chatId]).length : 0,\n  replyIndex_len:    Array.isArray(sd.replyIndex?.[chatId]) ? sd.replyIndex[chatId].length : 0,\n};\n\n// Собираем message_id для зачистки в photoIndex:\n// 1) из текущего photoBucket[chatId].files\n// 2) опционально — из $json.deleted_message_ids (если кто-то сверху передал)\nconst toPruneSet = new Set();\n\n// from bucket\nconst bucketFiles = sd.photoBucket?.[chatId]?.files || [];\nfor (const x of bucketFiles) {\n  const mid = Number(x?.message_id);\n  if (Number.isFinite(mid) && mid > 0) toPruneSet.add(String(mid));\n}\n\n// from payload (optional)\nconst extraIds = Array.isArray($json.deleted_message_ids) ? $json.deleted_message_ids : [];\nfor (const mid of extraIds) {\n  const n = Number(mid);\n  if (Number.isFinite(n) && n > 0) toPruneSet.add(String(n));\n}\n\n// Clear bucket + replyIndex для этого чата\ndelete sd.photoBucket[chatId];\ndelete sd.replyIndex[chatId];\n\n// Prune только нужные ключи из photoIndex (не дропаем карту целиком)\nlet prunedCount = 0;\nif (sd.photoIndex[chatId] && toPruneSet.size > 0) {\n  for (const key of toPruneSet) {\n    if (Object.prototype.hasOwnProperty.call(sd.photoIndex[chatId], key)) {\n      delete sd.photoIndex[chatId][key];\n      prunedCount++;\n    }\n  }\n  // Можно было бы удалить sd.photoIndex[chatId], если она опустела,\n  // но оставляем, чтобы не дёргать структуру.\n}\n\n// AFTER snapshot\nconst after = {\n  photoBucket_files: sd.photoBucket?.[chatId]?.files?.length || 0,\n  photoIndex_keys:   sd.photoIndex?.[chatId] ? Object.keys(sd.photoIndex[chatId]).length : 0,\n  replyIndex_len:    Array.isArray(sd.replyIndex?.[chatId]) ? sd.replyIndex[chatId].length : 0,\n};\n\nreturn [{\n  json: {\n    ok: true,\n    chatId,\n    pruned_from_photoIndex: prunedCount,\n    before,\n    after\n  }\n}];\n"
        },
        "type": "n8n-nodes-base.code",
        "typeVersion": 2,
        "position": [
          2656,
          2304
        ],
        "id": "07cb610e-d636-4d67-bbf6-6648a048af39",
        "name": "Clean_memory"
      },
      {
        "parameters": {
          "jsCode": "// n8n Code — FLUX: price + batch + aspect (w,h)\n\n// Ожидаем, что в $json уже есть:\n// - ar       — строка формата \"16:9\", \"3:4\" и т.п. (дефолт \"1:1\")\n// - quality  — 0 | 1 | 2  (дефолт 1)\n// - batch / batch_count — на будущее, сейчас из вебаппа не приходит (дефолт 1)\n\nconst arRaw = $json.ar ?? '1:1';\nlet quality = Number($json.quality);\nif (!Number.isFinite(quality) || quality < 0 || quality > 2) {\n  quality = 1; // дефолт Standard\n}\n\n// --- Таблица по качеству ---\n// Можно интерпретировать так: \"стоимость одной картинки в кредитах\"\nconst QUALITY_TABLE = {\n  0: { label: 'light',   credits: 1.5 },\n  1: { label: 'standard',credits: 2.0 },\n  2: { label: 'pro',     credits: 3.0 },\n};\n\n// --- Таблица по батчу (множитель) ---\n// batch = 1 → множитель 1\n// batch = 2 → 1.75\n// batch = 4 → 3\nlet batchCount = Number($json.batch_count ?? $json.batch ?? 1);\nif (!Number.isFinite(batchCount) || ![1, 2, 4].includes(batchCount)) {\n  batchCount = 1;\n}\n\nconst BATCH_TABLE = {\n  1: { multiplier: 1.0 },\n  2: { multiplier: 1.75 },\n  4: { multiplier: 3.0 },\n};\n\n// Берём конфиги (с фолбэком на дефолты)\nconst qCfg = QUALITY_TABLE[quality] ?? QUALITY_TABLE[1];\nconst bCfg = BATCH_TABLE[batchCount] ?? BATCH_TABLE[1];\n\n// Итоговая цена в кредитах\nconst creditsCost = qCfg.credits * bCfg.multiplier;\n\n// --- aspect → w,h ---\n// arRaw, например, \"16:9\"\nconst parts = String(arRaw).split(':');\nconst w = parseInt(parts[0], 10) || 1;\nconst h = parseInt(parts[1], 10) || 1;\n\n// Собираем расширенный json\nreturn [{\n  json: {\n    ...$json,\n    quality,\n    batch_count: batchCount,\n    price: creditsCost,\n    w,\n    h,\n    quality,\n    batch_count: batchCount,\n  }\n}];\n"
        },
        "type": "n8n-nodes-base.code",
        "typeVersion": 2,
        "position": [
          1840,
          2272
        ],
        "id": "6377556e-b9a2-41a8-b92e-2add18236be4",
        "name": "flux_price_and_aspect"
      },
      {
        "parameters": {
          "workflowId": {
            "__rl": true,
            "value": "leS7vcfjDNDlHg0o",
            "mode": "list",
            "cachedResultUrl": "/workflow/leS7vcfjDNDlHg0o",
            "cachedResultName": "Geni_AI_FLUX_v002"
          },
          "workflowInputs": {
            "mappingMode": "defineBelow",
            "value": {},
            "matchingColumns": [],
            "schema": [],
            "attemptToConvertTypes": false,
            "convertFieldsToString": true
          },
          "options": {
            "waitForSubWorkflow": true
          }
        },
        "type": "n8n-nodes-base.executeWorkflow",
        "typeVersion": 1.2,
        "position": [
          2304,
          2304
        ],
        "name": "Call FLUX",
        "id": "1919d1ec-14fd-4573-ad60-9a233c235d90"
      },
      {
        "parameters": {
          "jsCode": "// Seedream photo-bucket controller (унификация с Seedance)\n// Авто-режим: если есть фото → i2i; иначе → t2i.\n// Настройки читаем из sd.seedream[chatId] (quality 1k/2k/4k, ratio для t2i).\n\nconst sd = $getWorkflowStaticData('global');\nsd.photoBucket = sd.photoBucket || {};   // { [chatId]: { files:[{file_id,...}], savedAt } }\nsd.seedream    = sd.seedream    || {};   // { [chatId]: { quality, ratio, dims_t2i, long_edge_i2i, savedAt } }\n\n// ---------- inputs (normalized + telegram) ----------\nconst msg    = $json.message || {};\nconst chatId = String($json.chatId || msg.chat?.id || $json.chat?.id || '');\nconst tool   = $json.tool || 'seedream';\n\nconst normalizedPrompt = typeof $json.prompt === 'string' ? $json.prompt : '';\nconst tgText = typeof msg.text === 'string' ? msg.text : (typeof msg.caption === 'string' ? msg.caption : '');\nconst prompt = (normalizedPrompt || tgText || '').trim();\nconst hasText = prompt.length > 0;\n\n// reply target id (для reply_to_message_id)\nconst reply_to_message_id = (msg.message_id ?? $json.deleteMessageId ?? null);\n\n// ---------- Настройки Seedream ----------\n// Если не сохранены мини-аппом — подставим дефолты и посчитаем размеры\nfunction buildDefaultSeedream() {\n  // база 2K (из доки), и масштабируем на 1k/4k\n  const BASE_2K = {\n    \"1:1\":  { w: 2048, h: 2048 },\n    \"4:3\":  { w: 2304, h: 1728 },\n    \"3:4\":  { w: 1728, h: 2304 },\n    \"16:9\": { w: 2560, h: 1440 },\n    \"9:16\": { w: 1440, h: 2560 },\n    \"3:2\":  { w: 2496, h: 1664 },\n    \"2:3\":  { w: 1664, h: 2496 },\n    \"21:9\": { w: 3024, h: 1296 },\n  };\n  const SCALES = { '1k':0.5, '2k':1.0, '4k':2.0 };\n  const quality = '2k';\n  const ratio   = '1:1';\n  const dims    = BASE_2K[ratio];\n  return {\n    quality,\n    ratio,\n    dims_t2i: { w: dims.w, h: dims.h },\n    long_edge_i2i: 2048, // для i2i\n    savedAt: new Date().toISOString()\n  };\n}\n\nconst cfg0 = sd.seedream[chatId] || buildDefaultSeedream();\n// валидируем минимально\nconst quality = ['1k','2k','4k'].includes(String(cfg0.quality)) ? cfg0.quality : '2k';\nconst ratio   = [\"1:1\",\"4:3\",\"3:4\",\"16:9\",\"9:16\",\"3:2\",\"2:3\",\"21:9\"].includes(String(cfg0.ratio)) ? cfg0.ratio : '1:1';\nconst dims_t2i = (cfg0.dims_t2i && Number(cfg0.dims_t2i.w) && Number(cfg0.dims_t2i.h)) ? cfg0.dims_t2i : null;\nconst long_edge_i2i = Number(cfg0.long_edge_i2i) || (quality==='1k'?1024:(quality==='4k'?4096:2048));\n\nconst cfg = { quality, ratio, dims_t2i, long_edge_i2i, savedAt: cfg0.savedAt };\n\n// формат настройки для текста\nfunction fmtSettingsSeedream(c) {\n  const q = c.quality.toUpperCase();\n  const ar = c.ratio;\n  const dims = c.dims_t2i ? `${c.dims_t2i.w}×${c.dims_t2i.h}` : '—';\n  return [\n    `🧩 Mode: auto (t2i/i2i)`,\n    `🖼 Quality: ${q}`,\n    `📐 Aspect (t2i): ${ar} · ${dims}`,\n    `↔️ Long edge (i2i): ${c.long_edge_i2i}`\n  ].join('\\n');\n}\n\n// ---------- detect incoming image ----------\nlet incoming = null;\n\n// 1) нормализованный photo_file_id\nif ($json.photo_file_id) {\n  incoming = {\n    file_id: $json.photo_file_id,\n    kind: $json.file_kind || 'photo',\n    file_name: $json.file_name || null,\n    mime_type: $json.mime_type || null,\n    message_id: (msg.message_id ?? $json.deleteMessageId ?? null),\n    savedAt: new Date().toISOString()\n  };\n}\n\n// 2) Telegram document\nif (!incoming && msg.document?.file_id) {\n  const mt   = String(msg.document.mime_type || '').toLowerCase();\n  const name = String(msg.document.file_name || '');\n  const looksImage = mt.startsWith('image/') || /\\.(png|jpe?g|webp|bmp|gif|tiff?)$/i.test(name);\n  if (looksImage) {\n    incoming = {\n      file_id: msg.document.file_id,\n      kind: 'document',\n      file_name: name || null,\n      mime_type: msg.document.mime_type || null,\n      message_id: (msg.message_id ?? $json.deleteMessageId ?? null),\n      savedAt: new Date().toISOString()\n    };\n  }\n}\n\n// 3) Telegram photo[]\nif (!incoming && Array.isArray(msg.photo) && msg.photo.length) {\n  const best = msg.photo[msg.photo.length - 1];\n  incoming = {\n    file_id: best.file_id,\n    kind: 'photo',\n    file_name: null,\n    mime_type: null,\n    message_id: (msg.message_id ?? $json.deleteMessageId ?? null),\n    savedAt: new Date().toISOString()\n  };\n}\n\n// ---------- guards & commands ----------\nif (!chatId) {\n  return [{ json: { action: 'ERROR', reason: 'no_chat', got: Object.keys($json), reply_to_message_id } }];\n}\n\n// /clear очистка корзины\nif (/^\\/clear\\b/i.test(prompt)) {\n  delete sd.photoBucket[chatId];\n  return [{ json: { action:'CLEARED', chatId, reply:'🗑️ Копилка фото очищена.', settings_seedream: cfg, settings_text: fmtSettingsSeedream(cfg), reply_to_message_id } }];\n}\n\n// ---------- helpers ----------\nfunction ensureBucket() { return sd.photoBucket[chatId] ?? { files: [], savedAt: new Date().toISOString() }; }\nfunction pushLimited(b, f, limit=10) {\n  const exists = (b.files || []).some(x => x.file_id === f.file_id);\n  if (!exists) {\n    (b.files = b.files || []).push(f);\n    if (b.files.length > limit) b.files = b.files.slice(-limit);\n  }\n  return b;\n}\nfunction kbClearBucket() {\n  return [[ { text: '🗑 Очистить', callback_data: 'seedream:clear' } ]];\n}\n\n// ====================================================\n// ===================== FLOW =========================\n// ====================================================\n\n// 1) Фото БЕЗ текста → STORED (i2i будет возможен после текста)\nif (incoming && !hasText) {\n  const bucket = pushLimited(ensureBucket(), incoming, 10);\n  sd.photoBucket[chatId] = bucket;\n  return [{\n    json: {\n      action: 'STORED',\n      chatId,\n      tool,\n      storedCount: bucket.files.length,\n      reply: `📥 Фото сохранено (${bucket.files.length}). Теперь отправьте текст — запущу i2i.\\n\\n${fmtSettingsSeedream(cfg)}`,\n      inline_keyboard: kbClearBucket(),\n      settings_seedream: cfg,\n      settings_text: fmtSettingsSeedream(cfg),\n      reply_to_message_id\n    }\n  }];\n}\n\n// 2) Решение по запуску\nconst bucket = sd.photoBucket[chatId] || { files: [] };\nconst files  = bucket.files || [];\nconst hasStoredPhoto = files.length > 0;\n\n// Если есть текст и НЕТ фото → t2i\nif (hasText && !hasStoredPhoto && !incoming) {\n  // ожидается генерация t2i: из настроек отдаём dims_t2i\n  return [{\n    json: {\n      action: 'GENERATE',\n      mode: 't2i',\n      chatId,\n      tool,\n      prompt,\n      files: [],\n      wants_base64: false,\n      clearAfter: false,\n      next: 'BUILD_T2I_REQUEST',\n      // t2i параметры из настроек (если null — посчитаешь дальше)\n      t2i_dims: cfg.dims_t2i || null,  // {w,h} или null\n      t2i_ratio: cfg.ratio,            // '1:1' ...\n      t2i_quality: cfg.quality,        // '1k'|'2k'|'4k'\n      settings_seedream: cfg,\n      settings_text: fmtSettingsSeedream(cfg),\n      reply_to_message_id\n    }\n  }];\n}\n\n// /clear очистка корзины\nif (/^\\/clear\\b/i.test(prompt)) {\n  delete sd.photoBucket[chatId];\n  return [{\n    json: {\n      action: 'CLEARED',\n      chatId,\n      reply: '🗑️ Копилка фото очищена.',\n      settings_seedream: cfg,\n      settings_text: fmtSettingsSeedream(cfg),\n      reply_to_message_id\n    }\n  }];\n}\n\n// Если есть текст и ЕСТЬ фото (или пришло вместе) → i2i\nif (hasText && (hasStoredPhoto || incoming)) {\n  const all = hasStoredPhoto ? files.slice() : [];\n  if (incoming) all.push(incoming);\n  return [{\n    json: {\n      action: 'GENERATE',\n      mode: 'i2i',\n      chatId,\n      tool,\n      prompt,\n      files: all,               // массив {file_id,...}\n      primary_file: all[all.length-1] || null,\n      wants_base64: true,\n      clearAfter: true,         // после старта очищаем корзину\n      next: 'FETCH_FILE_BASE64',\n      // i2i целевая \"длинная сторона\"\n      i2i_long_edge: cfg.long_edge_i2i,\n      settings_seedream: cfg,\n      settings_text: fmtSettingsSeedream(cfg),\n      reply_to_message_id\n    }\n  }];\n}\n\n// Если текста нет и фото нет → подсказка\nif (!hasText && !incoming && !hasStoredPhoto) {\n  return [{\n    json: {\n      action: 'REMIND',\n      chatId,\n      tool,\n      reply: `Пришлите текст — запущу t2i, или пришлите фото, затем текст — запущу i2i.\\n\\n${fmtSettingsSeedream(cfg)}`,\n      settings_seedream: cfg,\n      settings_text: fmtSettingsSeedream(cfg),\n      reply_to_message_id\n    }\n  }];\n}\n\n// Фолбек (например, пришло фото и ещё фото без текста)\nreturn [{\n  json: {\n    action: 'NOOP',\n    chatId,\n    tool,\n    reply: `Отправьте текст для запуска.\\n\\n${fmtSettingsSeedream(cfg)}`,\n    settings_seedream: cfg,\n    settings_text: fmtSettingsSeedream(cfg),\n    reply_to_message_id\n  }\n}];\n"
        },
        "type": "n8n-nodes-base.code",
        "typeVersion": 2,
        "position": [
          1664,
          3072
        ],
        "id": "ef8f52dd-7dba-451a-a8e3-12cff8aa7384",
        "name": "Seedream — photo store & launch"
      },
      {
        "parameters": {
          "jsCode": "// Seedream — builder for Comet /v1/images/generations (t2i: WIDTHxHEIGHT, i2i: 1k/2k/4k)\n\nconst MODEL = 'bytedance-seedream-4-0-250828';\n\n// 1) Входы\nconst cfg    = $json.settings_seedream || {};\nconst prompt = String($json.prompt || '').trim();\nconst n      = Number.isFinite(Number($json.n)) ? Number($json.n) : 1;\n\n// helpers\nfunction isDataUrl(u){ return typeof u === 'string' && /^data:image\\/[a-z0-9.+-]+;base64,/i.test(u); }\nfunction isHttp(u){ return typeof u === 'string' && /^https?:\\/\\//i.test(u); }\nfunction extractImages(src){\n  const out = [];\n  if (!src) return out;\n  if (Array.isArray(src)) {\n    for (const it of src) {\n      if (typeof it === 'string' && (isDataUrl(it) || isHttp(it))) out.push(it);\n      else if (it && typeof it === 'object') {\n        const cand = it.dataUrl || it.url || it.data || it.image_url || it.imageUrl;\n        if (typeof cand === 'string' && (isDataUrl(cand) || isHttp(cand))) out.push(cand);\n      }\n    }\n  } else if (typeof src === 'string' && (isDataUrl(src) || isHttp(src))) {\n    out.push(src);\n  } else if (src && typeof src === 'object') {\n    const cand = src.dataUrl || src.url || src.data || src.image_url || src.imageUrl;\n    if (typeof cand === 'string' && (isDataUrl(cand) || isHttp(cand))) out.push(cand);\n  }\n  return out;\n}\n\n// 2) Соберём изображения (если есть — это i2i)\nlet images = [];\nimages = images.concat(extractImages($json.dataUrls));\nimages = images.concat(extractImages($json.images));\nimages = images.concat(extractImages($json.dataUrl));\nimages = images.concat(extractImages($json.image_url));\nimages = Array.from(new Set(images));\n\nconst isI2I = images.length > 0;\n\n// 3) Определяем size\nlet sizeValue;\n\n// t2i → WIDTHxHEIGHT\nif (!isI2I) {\n  const dims = $json.t2i_dims || cfg.dims_t2i || null;\n  let w = Number(dims?.w), h = Number(dims?.h);\n  if (!(Number.isFinite(w) && Number.isFinite(h) && w>0 && h>0)) {\n    // fallback по качеству/соотношению, если вдруг dims не пришли\n    // по умолчанию 2k квадрат\n    const q = (cfg.quality || '2k').toLowerCase();\n    if (q === '1k') { w = 1024; h = 1024; }\n    else if (q === '4k') { w = 4096; h = 4096; }\n    else { w = 2048; h = 2048; }\n  }\n  sizeValue = `${Math.round(w)}x${Math.round(h)}`;\n} else {\n  // i2i → '1k'|'2k'|'4k' (как раньше)\n  let size = (cfg.quality || '2k').toLowerCase();\n  if (!['1k','2k','4k'].includes(size)) size = '2k';\n  sizeValue = size;\n}\n\n// 4) Сборка payload\nconst body = {\n  model: MODEL,\n  prompt: prompt,\n  n: n,\n  size: sizeValue,\n  response_format: 'url',\n  watermark: false\n};\nif (isI2I) body.image = images;\n\n// 5) Выход\nreturn [{\n  json: {\n    method: 'POST',\n    url: 'https://api.cometapi.com/v1/images/generations',\n    headers: { 'Content-Type': 'application/json' },\n    body\n  }\n}];\n"
        },
        "type": "n8n-nodes-base.code",
        "typeVersion": 2,
        "position": [
          2384,
          3440
        ],
        "id": "3ef7d833-7c59-4a20-b67b-8461aa027bf7",
        "name": "Нормализовать task_id1",
        "alwaysOutputData": false
      },
      {
        "parameters": {
          "rules": {
            "values": [
              {
                "conditions": {
                  "options": {
                    "caseSensitive": true,
                    "leftValue": "",
                    "typeValidation": "strict",
                    "version": 2
                  },
                  "conditions": [
                    {
                      "id": "6337ea59-4635-406e-89d4-62505dae4f35",
                      "leftValue": "={{ $json.action }}",
                      "rightValue": "STORED",
                      "operator": {
                        "type": "string",
                        "operation": "equals"
                      }
                    }
                  ],
                  "combinator": "and"
                }
              },
              {
                "conditions": {
                  "options": {
                    "caseSensitive": true,
                    "leftValue": "",
                    "typeValidation": "strict",
                    "version": 2
                  },
                  "conditions": [
                    {
                      "id": "d6fc6a81-7010-4119-9d91-c66d79e68ac8",
                      "leftValue": "={{ $json.action }}",
                      "rightValue": "=GENERATE",
                      "operator": {
                        "type": "string",
                        "operation": "equals"
                      }
                    }
                  ],
                  "combinator": "and"
                }
              },
              {
                "conditions": {
                  "options": {
                    "caseSensitive": true,
                    "leftValue": "",
                    "typeValidation": "strict",
                    "version": 2
                  },
                  "conditions": [
                    {
                      "id": "9cff2c53-bb3e-49bf-9cfe-53963eac8978",
                      "leftValue": "={{ $json.action }}",
                      "rightValue": "CLEARED",
                      "operator": {
                        "type": "string",
                        "operation": "equals",
                        "name": "filter.operator.equals"
                      }
                    }
                  ],
                  "combinator": "and"
                }
              },
              {
                "conditions": {
                  "options": {
                    "caseSensitive": true,
                    "leftValue": "",
                    "typeValidation": "strict",
                    "version": 2
                  },
                  "conditions": [
                    {
                      "id": "39ccebaf-cc95-4d0b-954f-71fab6178691",
                      "leftValue": "={{ $json.action }}",
                      "rightValue": "NOOP ",
                      "operator": {
                        "type": "string",
                        "operation": "equals",
                        "name": "filter.operator.equals"
                      }
                    }
                  ],
                  "combinator": "and"
                }
              },
              {
                "conditions": {
                  "options": {
                    "caseSensitive": true,
                    "leftValue": "",
                    "typeValidation": "strict",
                    "version": 2
                  },
                  "conditions": [
                    {
                      "id": "a9f7ce9b-cd18-4138-83f1-8cb70c4643ed",
                      "leftValue": "={{ $json.action }}",
                      "rightValue": "REMIND",
                      "operator": {
                        "type": "string",
                        "operation": "equals",
                        "name": "filter.operator.equals"
                      }
                    }
                  ],
                  "combinator": "and"
                }
              }
            ]
          },
          "options": {}
        },
        "type": "n8n-nodes-base.switch",
        "typeVersion": 3.2,
        "position": [
          2032,
          3024
        ],
        "id": "8f1d2ee7-ebaa-44be-996b-958adf1fbb1f",
        "name": "Switch"
      },
      {
        "parameters": {
          "jsCode": "const sd = $getWorkflowStaticData('global');\nsd.photoIndex  = sd.photoIndex  || {}; // { [chatId]: { [messageId]: file_id } }\n\nconst chatId = String($json.chatId || '');\nconst msgId  = Number($json.photo_message_id ?? $json.deleteMessageId ?? 0);\nconst fileId = $json.file_id || $json.photo_file_id || $json.primary_file?.file_id || null;\n\n\nif (chatId && msgId && fileId) {\n  sd.photoIndex[chatId] = sd.photoIndex[chatId] || {};\n  sd.photoIndex[chatId][msgId] = fileId;\n}\n\nreturn [{ json: $json }];\n"
        },
        "type": "n8n-nodes-base.code",
        "typeVersion": 2,
        "position": [
          2336,
          2976
        ],
        "id": "4792537f-3ed4-4206-8839-bab0460b5cdf",
        "name": "Code"
      },
      {
        "parameters": {
          "jsCode": "const sd = $getWorkflowStaticData('global');\nsd.replyIndex = sd.replyIndex || {}; // { [chatId]: number[] }\n\nconst chatId = String($json.chatId || $json.result?.chat?.id || '');\nconst sentId =\n  Number($json.result?.message_id) ||\n  Number($json.result?.message?.message_id) ||\n  Number($json.message_id) || 0;\n\nif (chatId && sentId) {\n  sd.replyIndex[chatId] = sd.replyIndex[chatId] || [];\n  if (!sd.replyIndex[chatId].includes(sentId)) sd.replyIndex[chatId].push(sentId);\n}\n\nreturn [{ json: $json }];\n"
        },
        "type": "n8n-nodes-base.code",
        "typeVersion": 2,
        "position": [
          2672,
          2976
        ],
        "id": "c566fc05-b832-407f-91a0-e1b2570df49c",
        "name": "Code24"
      },
      {
        "parameters": {
          "chatId": "={{ $json.chatId }}",
          "text": "={{ $json.lang === 'en'\n  ? `${$json.reply}\\n⚡ Generation cost: ${$json.price} credits.\\nTap a button to delete.`\n  : `${$json.reply}\\n⚡Стоимость генерации: ${$json.price} кредитов.\\nНажмите кнопку, чтобы удалить.`\n}}",
          "replyMarkup": "inlineKeyboard",
          "inlineKeyboard": {
            "rows": [
              {
                "row": {
                  "buttons": [
                    {
                      "text": "={{ $json.lang === 'en'\n  ? '🗑 Delete this photo'\n  : '🗑 Удалить это фото'\n}}",
                      "additionalFields": {
                        "callback_data": "del:${$json.photo_message_id}"
                      }
                    }
                  ]
                }
              },
              {
                "row": {
                  "buttons": [
                    {
                      "text": "={{ $json.lang === 'en'\n  ? '🧺 Clear all'\n  : '🧺 Очистить все'\n}}",
                      "additionalFields": {
                        "callback_data": "del_all"
                      }
                    }
                  ]
                }
              }
            ]
          },
          "additionalFields": {
            "appendAttribution": false,
            "reply_to_message_id": "={{ $json.reply_to_message_id }}"
          }
        },
        "id": "8cc27531-b8e6-4985-a60b-0826c028b6f9",
        "name": "Telegram",
        "type": "n8n-nodes-base.telegram",
        "typeVersion": 1.2,
        "position": [
          2496,
          2976
        ],
        "webhookId": "6969c97f-2c91-44e1-ae46-426d11c99897",
        "credentials": {
          "telegramApi": {
            "id": "ur7jSUPdiAaPVhCf",
            "name": "Geni AI"
          }
        }
      }
    ],
    "connections": {
      "Switch1": {
        "main": [
          [
            {
              "node": "Switch_lang_act",
              "type": "main",
              "index": 0
            }
          ],
          [
            {
              "node": "lang_inline",
              "type": "main",
              "index": 0
            }
          ],
          [
            {
              "node": "Instruct_mes",
              "type": "main",
              "index": 0
            }
          ],
          [
            {
              "node": "Seedream — photo store & launch",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "Telegram Trigger1": {
        "main": [
          [
            {
              "node": "Switch_message_type",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "reply_markup": {
        "main": [
          [
            {
              "node": "deleteMessageMenu",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "menu": {
        "main": [
          [
            {
              "node": "switch_start",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "build_sub_invoice": {
        "main": [
          [
            {
              "node": "createInvoiceLink",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "payment.precheckout.parse": {
        "main": [
          [
            {
              "node": "answerPreCheckoutQuery",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "createInvoiceLink": {
        "main": [
          [
            {
              "node": "send_mes_inline_buy_plan",
              "type": "main",
              "index": 0
            },
            {
              "node": "delMes1",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "answerPreCheckoutQuery": {
        "main": [
          []
        ]
      },
      "payment.parse_success": {
        "main": [
          [
            {
              "node": "apply_payment",
              "type": "main",
              "index": 0
            },
            {
              "node": "payment_pick_prompt_to_delete",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "apply_payment": {
        "main": [
          [
            {
              "node": "send_mes_payment",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "Switch_message_type": {
        "main": [
          [
            {
              "node": "payment.precheckout.parse",
              "type": "main",
              "index": 0
            }
          ],
          [
            {
              "node": "payment.parse_success",
              "type": "main",
              "index": 0
            }
          ],
          [
            {
              "node": "message_reply_inline",
              "type": "main",
              "index": 0
            }
          ],
          [
            {
              "node": "Switch_web_app",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "Switch_web_app": {
        "main": [
          [
            {
              "node": "parse web_app_data",
              "type": "main",
              "index": 0
            }
          ],
          [
            {
              "node": "menu",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "switch_reply_menu": {
        "main": [
          [
            {
              "node": "reply_markup",
              "type": "main",
              "index": 0
            }
          ],
          [
            {
              "node": "Switch1",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "parse web_app_data": {
        "main": [
          []
        ]
      },
      "lang_inline": {
        "main": [
          [
            {
              "node": "delMes",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "delMes_keyboardMsg1": {
        "main": [
          []
        ]
      },
      "Switch_lang_act": {
        "main": [
          [
            {
              "node": "id_and_balance",
              "type": "main",
              "index": 0
            }
          ],
          [
            {
              "node": "send_mes_plan",
              "type": "main",
              "index": 0
            }
          ],
          [
            {
              "node": "build_sub_invoice",
              "type": "main",
              "index": 0
            }
          ],
          [
            {
              "node": "build_credits_packs",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "message_reply_inline": {
        "main": [
          [
            {
              "node": "Switch_inline_act",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "Switch_inline_act": {
        "main": [
          [
            {
              "node": "delMes_targetMsg",
              "type": "main",
              "index": 0
            },
            {
              "node": "delMes_keyboardMsg",
              "type": "main",
              "index": 0
            },
            {
              "node": "Delete ONE image from memory",
              "type": "main",
              "index": 0
            }
          ],
          [
            {
              "node": "Build items-to-delete",
              "type": "main",
              "index": 0
            }
          ],
          [
            {
              "node": "SB_set_user_lang",
              "type": "main",
              "index": 0
            }
          ],
          [
            {
              "node": "build_credits_invoice",
              "type": "main",
              "index": 0
            },
            {
              "node": "delMes_keyboardMsg2",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "SB_set_user_lang": {
        "main": [
          [
            {
              "node": "delMes_keyboardMsg1",
              "type": "main",
              "index": 0
            },
            {
              "node": "send_mes_lang_save",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "Build items-to-delete": {
        "main": [
          [
            {
              "node": "delMes2",
              "type": "main",
              "index": 0
            },
            {
              "node": "Clear ALL buckets/indexes",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "send_mes_balance": {
        "main": [
          [
            {
              "node": "delMes1",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "send_mes_plan": {
        "main": [
          [
            {
              "node": "delMes1",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "Clear ALL buckets/indexes": {
        "main": [
          [
            {
              "node": "send_mes_clear",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "build_credits_packs": {
        "main": [
          [
            {
              "node": "reply_markup1",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "createInvoiceLink_credits": {
        "main": [
          [
            {
              "node": "Merge",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "reply_markup1": {
        "main": [
          [
            {
              "node": "delMes1",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "build_credits_invoice": {
        "main": [
          [
            {
              "node": "createInvoiceLink_credits",
              "type": "main",
              "index": 0
            },
            {
              "node": "create_payment_yookassa_credits",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "send_mes_payment1": {
        "main": [
          [
            {
              "node": "payment_store_prompt",
              "type": "main",
              "index": 0
            },
            {
              "node": "store_payment_message",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "payment_pick_prompt_to_delete": {
        "main": [
          [
            {
              "node": "delMes3",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "welcome_once": {
        "main": [
          [
            {
              "node": "switch_welcome",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "switch_start": {
        "main": [
          [
            {
              "node": "ensure_user",
              "type": "main",
              "index": 0
            }
          ],
          [
            {
              "node": "switch_reply_menu",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "switch_welcome": {
        "main": [
          [
            {
              "node": "send_wel_cred",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "send_mes_lang_save": {
        "main": [
          [
            {
              "node": "lang_to_home_after_set",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "lang_to_home_after_set": {
        "main": [
          [
            {
              "node": "Switch_message_type",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "delMes": {
        "main": [
          []
        ]
      },
      "delMes4": {
        "main": [
          [
            {
              "node": "lang_to_home_after_set1",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "lang_to_home_after_set1": {
        "main": [
          [
            {
              "node": "Switch_message_type",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "Instruct_mes": {
        "main": [
          [
            {
              "node": "delMes4",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "switch_home_inline": {
        "main": [
          [
            {
              "node": "send_home_inline",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "Merge": {
        "main": [
          [
            {
              "node": "send_mes_payment1",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "create_payment_yookassa_credits": {
        "main": [
          [
            {
              "node": "Merge",
              "type": "main",
              "index": 1
            }
          ]
        ]
      },
      "id_and_balance": {
        "main": [
          [
            {
              "node": "send_mes_balance",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "ensure_user": {
        "main": [
          [
            {
              "node": "welcome_once",
              "type": "main",
              "index": 0
            },
            {
              "node": "send_start_mes",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "Switch2": {
        "main": [
          [
            {
              "node": "Code15",
              "type": "main",
              "index": 0
            }
          ],
          [
            {
              "node": "Call FLUX",
              "type": "main",
              "index": 0
            }
          ],
          [
            {
              "node": "Telegram19",
              "type": "main",
              "index": 0
            }
          ],
          [
            {
              "node": "Telegram18",
              "type": "main",
              "index": 0
            }
          ],
          [
            {
              "node": "Telegram18",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "Telegram20": {
        "main": [
          [
            {
              "node": "Code23",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "Code15": {
        "main": [
          [
            {
              "node": "Telegram20",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "Flux settings": {
        "main": [
          [
            {
              "node": "flux_price_and_aspect",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "flux_price_and_aspect": {
        "main": [
          [
            {
              "node": "Switch2",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "Call FLUX": {
        "main": [
          [
            {
              "node": "Clean_memory",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "Seedream — photo store & launch": {
        "main": [
          [
            {
              "node": "Switch",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "Switch": {
        "main": [
          [
            {
              "node": "Code",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "Code": {
        "main": [
          [
            {
              "node": "Telegram",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "Telegram": {
        "main": [
          [
            {
              "node": "Code24",
              "type": "main",
              "index": 0
            }
          ]
        ]
      }
    },
    "authors": "Grigoriy Voyakin",
    "name": null,
    "description": null
  }
}