AutomationFlowsAI & RAG › Telegram Legal RAG Chatbot

Telegram Legal RAG Chatbot

Original n8n title: Legal RAG Telegram API Current Github Ready

legal_rag_telegram_api_current_github_ready. Uses telegramTrigger, httpRequest. Event-driven trigger; 56 nodes.

Event trigger★★★★★ complexity56 nodesTelegram TriggerHTTP Request
AI & RAG Trigger: Event Nodes: 56 Complexity: ★★★★★ Added:

This workflow follows the HTTP Request → Telegram Trigger recipe pattern — see all workflows that pair these two integrations.

The workflow JSON

Copy or download the full n8n JSON below. Paste it into a new n8n workflow, add your credentials, activate. Full import guide →

Download .json
{
  "name": "legal_rag_telegram_api_current_github_ready",
  "nodes": [
    {
      "parameters": {
        "updates": [
          "message",
          "callback_query"
        ],
        "additionalFields": {}
      },
      "id": "343b97d5-02b2-4853-9dab-692872f64994",
      "name": "Telegram Trigger",
      "type": "n8n-nodes-base.telegramTrigger",
      "typeVersion": 1.2,
      "position": [
        46928,
        12496
      ],
      "credentials": {
        "telegramApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "const msg = $json.message || {};\nconst cb = $json.callback_query || {};\nconst doc = msg?.document || null;\n\nreturn [{\n  json: {\n    chat_id: msg?.chat?.id || cb?.message?.chat?.id || null,\n    input_text: (msg?.text || '').trim(),\n    callback_data: cb?.data || '',\n    callback_id: cb?.id || '',\n    telegram_file_id: doc?.file_id || '',\n    telegram_document_name: doc?.file_name || '',\n    telegram_mime_type: doc?.mime_type || '',\n    has_document: !!doc\n  },\n  binary: $binary\n}];"
      },
      "id": "3902c56f-19e6-4a53-ab07-25bedc9d9400",
      "name": "Normalize Input",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        47312,
        12384
      ]
    },
    {
      "parameters": {
        "jsCode": "const data = $getWorkflowStaticData('global');\nconst states = data.states || {};\nconst chatId = String($json.chat_id || 'unknown');\nconst state = states[chatId] || {};\n\nreturn [{\n  json: {\n    ...$json,\n    api_base_url: $env.N8N_API_BASE_URL || 'http://host.docker.internal:8000/api',\n    rag_base_url: $env.N8N_RAG_BASE_URL || 'http://rag:8001',\n    llm_base_url: $env.N8N_LLM_BASE_URL || 'http://llm:8002',\n    rag_top_k: Number($env.N8N_RAG_TOP_K || 5),\n    active_case_id: state.active_case_id || '',\n    pending_action: state.pending_action || ''\n  },\n  binary: $binary\n}];"
      },
      "id": "9cbc8406-2fe9-4d57-9d37-68ed0af5518b",
      "name": "Load State",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        47552,
        12384
      ]
    },
    {
      "parameters": {
        "jsCode": "const text = ($json.input_text || '').trim();\nconst cb = $json.callback_data || '';\nconst pending = $json.pending_action || '';\nconst hasActive = !!$json.active_case_id;\nconst hasDocument = !!$json.has_document;\n\nlet route = 'UNKNOWN';\nlet selected_case_id = '';\nlet selected_doc_id = '';\n\nif (text === '/start' || text === '/menu' || cb === 'BACK_MAIN') {\n  route = 'START';\n} else if (cb === 'BACK_LIST_CASES') {\n  route = 'LIST_CASES';\n} else if (cb === 'BACK_CASE_MENU') {\n  route = hasActive ? 'STATUS' : 'NO_ACTIVE_CASE';\n} else if (text === '/new_case' || text === '/newcase' || cb === 'MENU_NEW_CASE') {\n  route = 'NEW_CASE';\n} else if (pending === 'waiting_case_title' && text && !text.startsWith('/')) {\n  route = 'SUBMIT_NEW_CASE';\n} else if (text === '/list_cases' || text === '/listcases' || cb === 'MENU_LIST_CASES' || cb === 'CASE_LIST') {\n  route = 'LIST_CASES';\n} else if (cb.startsWith('CASE_SELECT:')) {\n  route = 'SELECT_CASE';\n  selected_case_id = cb.split(':')[1] || '';\n} else if (text.startsWith('/select ')) {\n  route = 'SELECT_CASE';\n  selected_case_id = text.replace('/select', '').trim();\n} else if (text === '/status' || cb === 'CASE_STATUS') {\n  route = hasActive ? 'STATUS' : 'NO_ACTIVE_CASE';\n} else if (cb === 'CASE_LIST_DOCS') {\n  route = hasActive ? 'LIST_DOCS' : 'NO_ACTIVE_CASE';\n} else if (cb === 'CASE_DELETE_DOC') {\n  route = hasActive ? 'DELETE_DOC' : 'NO_ACTIVE_CASE';\n} else if (cb.startsWith('DOC_DELETE:')) {\n  route = hasActive ? 'CONFIRM_DELETE_DOC' : 'NO_ACTIVE_CASE';\n  selected_doc_id = cb.split(':')[1] || '';\n} else if (text === '/add_doc' || text === '/adddoc' || cb === 'CASE_ADD_DOC') {\n  route = hasActive ? 'ADD_DOC' : 'NO_ACTIVE_CASE';\n} else if (pending === 'waiting_document' && hasDocument) {\n  route = hasActive ? 'SUBMIT_ADD_DOC' : 'NO_ACTIVE_CASE';\n} else if (text === '/ask' || cb === 'CASE_ASK') {\n  route = hasActive ? 'ASK' : 'NO_ACTIVE_CASE';\n} else if (pending === 'waiting_question' && text && !text.startsWith('/')) {\n  route = hasActive ? 'SUBMIT_ASK' : 'NO_ACTIVE_CASE';\n}\n\nreturn [{\n  json: {\n    ...$json,\n    route,\n    selected_case_id,\n    selected_doc_id\n  },\n  binary: $binary\n}];"
      },
      "id": "e61f36ff-4e44-463e-824c-c638d1f508f7",
      "name": "Determine Route",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        47792,
        12384
      ]
    },
    {
      "parameters": {
        "rules": {
          "values": [
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "typeValidation": "strict",
                  "version": 2
                },
                "conditions": [
                  {
                    "leftValue": "={{$json.route}}",
                    "rightValue": "START",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    }
                  }
                ],
                "combinator": "and"
              }
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "typeValidation": "strict",
                  "version": 2
                },
                "conditions": [
                  {
                    "leftValue": "={{$json.route}}",
                    "rightValue": "NEW_CASE",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    }
                  }
                ],
                "combinator": "and"
              }
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "typeValidation": "strict",
                  "version": 2
                },
                "conditions": [
                  {
                    "leftValue": "={{$json.route}}",
                    "rightValue": "SUBMIT_NEW_CASE",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    }
                  }
                ],
                "combinator": "and"
              }
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "typeValidation": "strict",
                  "version": 2
                },
                "conditions": [
                  {
                    "leftValue": "={{$json.route}}",
                    "rightValue": "LIST_CASES",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    }
                  }
                ],
                "combinator": "and"
              }
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "typeValidation": "strict",
                  "version": 2
                },
                "conditions": [
                  {
                    "leftValue": "={{$json.route}}",
                    "rightValue": "SELECT_CASE",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    }
                  }
                ],
                "combinator": "and"
              }
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "typeValidation": "strict",
                  "version": 2
                },
                "conditions": [
                  {
                    "leftValue": "={{$json.route}}",
                    "rightValue": "STATUS",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    }
                  }
                ],
                "combinator": "and"
              }
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "typeValidation": "strict",
                  "version": 2
                },
                "conditions": [
                  {
                    "leftValue": "={{$json.route}}",
                    "rightValue": "LIST_DOCS",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    }
                  }
                ],
                "combinator": "and"
              }
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "typeValidation": "strict",
                  "version": 2
                },
                "conditions": [
                  {
                    "leftValue": "={{$json.route}}",
                    "rightValue": "DELETE_DOC",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    }
                  }
                ],
                "combinator": "and"
              }
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "typeValidation": "strict",
                  "version": 2
                },
                "conditions": [
                  {
                    "leftValue": "={{$json.route}}",
                    "rightValue": "CONFIRM_DELETE_DOC",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    }
                  }
                ],
                "combinator": "and"
              }
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "typeValidation": "strict",
                  "version": 2
                },
                "conditions": [
                  {
                    "leftValue": "={{$json.route}}",
                    "rightValue": "ADD_DOC",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    }
                  }
                ],
                "combinator": "and"
              }
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "typeValidation": "strict",
                  "version": 2
                },
                "conditions": [
                  {
                    "leftValue": "={{$json.route}}",
                    "rightValue": "SUBMIT_ADD_DOC",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    }
                  }
                ],
                "combinator": "and"
              }
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "typeValidation": "strict",
                  "version": 2
                },
                "conditions": [
                  {
                    "leftValue": "={{$json.route}}",
                    "rightValue": "ASK",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    }
                  }
                ],
                "combinator": "and"
              }
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "typeValidation": "strict",
                  "version": 2
                },
                "conditions": [
                  {
                    "leftValue": "={{$json.route}}",
                    "rightValue": "SUBMIT_ASK",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    }
                  }
                ],
                "combinator": "and"
              }
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "typeValidation": "strict",
                  "version": 2
                },
                "conditions": [
                  {
                    "leftValue": "={{$json.route}}",
                    "rightValue": "NO_ACTIVE_CASE",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    }
                  }
                ],
                "combinator": "and"
              }
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "typeValidation": "strict",
                  "version": 2
                },
                "conditions": [
                  {
                    "leftValue": "={{$json.route}}",
                    "rightValue": "UNKNOWN",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    }
                  }
                ],
                "combinator": "and"
              }
            }
          ]
        },
        "options": {}
      },
      "id": "b0560bf0-46dd-4c0a-81e3-d75085036788",
      "name": "Switch",
      "type": "n8n-nodes-base.switch",
      "typeVersion": 3.2,
      "position": [
        48032,
        12384
      ]
    },
    {
      "parameters": {
        "jsCode": "const data = $getWorkflowStaticData('global');\nconst states = data.states || {};\nconst chatId = String($json.chat_id || 'unknown');\nstates[chatId] = states[chatId] || {};\nstates[chatId].pending_action = '';\ndata.states = states;\n\nreturn [{ json: { chat_id: $json.chat_id, message_text: '\u0413\u043b\u0430\u0432\u043d\u043e\u0435 \u043c\u0435\u043d\u044e', reply_markup_json: JSON.stringify({ inline_keyboard: [[{ text: '\u0421\u043e\u0437\u0434\u0430\u0442\u044c \u0434\u0435\u043b\u043e', callback_data: 'MENU_NEW_CASE' }, { text: '\u0421\u043f\u0438\u0441\u043e\u043a \u0434\u0435\u043b', callback_data: 'MENU_LIST_CASES' }]] }) } }];"
      },
      "id": "393d3616-e51e-4452-b7bf-1739648b6291",
      "name": "Prepare Start Message",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        48272,
        11936
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "=https://api.telegram.org/bot{{$env.TELEGRAM_BOT_TOKEN}}/sendMessage",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ {\"chat_id\": $json.chat_id, \"text\": $json.message_text, \"parse_mode\": \"HTML\", \"reply_markup\": JSON.parse($json.reply_markup_json)} }}",
        "options": {}
      },
      "id": "6fd0d545-6882-4110-84ed-33dcc9829e6c",
      "name": "Send Start Menu",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.3,
      "position": [
        48512,
        11936
      ]
    },
    {
      "parameters": {
        "jsCode": "const data = $getWorkflowStaticData('global');\nconst states = data.states || {};\nconst chatId = String($json.chat_id || 'unknown');\nstates[chatId] = states[chatId] || {};\nstates[chatId].active_case_id = states[chatId].active_case_id || '';\nstates[chatId].pending_action = 'waiting_case_title';\ndata.states = states;\nreturn [{ json: { chat_id: $json.chat_id, message_text: '\u041d\u0430\u043f\u0438\u0448\u0438\u0442\u0435 \u043d\u0430\u0437\u0432\u0430\u043d\u0438\u0435 \u043d\u043e\u0432\u043e\u0433\u043e \u0434\u0435\u043b\u0430 \u043e\u0434\u043d\u0438\u043c \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435\u043c', reply_markup_json: JSON.stringify({ inline_keyboard: [[{ text: '\u041d\u0430\u0437\u0430\u0434', callback_data: 'BACK_MAIN' }]] }) } }];"
      },
      "id": "ec15aa73-c16c-4e69-aae1-28163d951958",
      "name": "Set Waiting Case Title",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        48272,
        12064
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "=https://api.telegram.org/bot{{$env.TELEGRAM_BOT_TOKEN}}/sendMessage",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ {\"chat_id\": $json.chat_id, \"text\": $json.message_text, \"parse_mode\": \"HTML\", \"reply_markup\": JSON.parse($json.reply_markup_json)} }}",
        "options": {}
      },
      "id": "7d462a8c-d631-46a6-a38c-9b1b87cab449",
      "name": "Ask Case Title",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.3,
      "position": [
        48512,
        12064
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "={{$json.api_base_url}}/cases",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Accept",
              "value": "application/json"
            },
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={\n  \"chat_id\": {{$json.chat_id}},\n  \"title\": \"{{$json.input_text}}\"\n}",
        "options": {
          "redirect": {},
          "response": {
            "response": {
              "fullResponse": false,
              "neverError": true,
              "responseFormat": "json"
            }
          }
        }
      },
      "id": "2930347d-49a1-4328-bcba-07cbc56b5715",
      "name": "API Create Case",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.3,
      "position": [
        48272,
        12176
      ]
    },
    {
      "parameters": {
        "jsCode": "const resp = $json || {};\nconst caseId = String(resp.id || resp.case_id || '');\nconst title = $item(0).$node['Determine Route'].json.input_text || '\u0411\u0435\u0437 \u043d\u0430\u0437\u0432\u0430\u043d\u0438\u044f';\n\nconst data = $getWorkflowStaticData('global');\nconst states = data.states || {};\nconst chatId = String($item(0).$node['Load State'].json.chat_id || 'unknown');\n\nstates[chatId] = states[chatId] || {};\nstates[chatId].active_case_id = caseId;\nstates[chatId].pending_action = '';\nstates[chatId].cases = Array.isArray(states[chatId].cases) ? states[chatId].cases : [];\nstates[chatId].case_titles = states[chatId].case_titles || {};\n\nconst existing = states[chatId].cases.find(c => String(c.case_id || c.id || '') === caseId);\nconst localCase = {\n  case_id: caseId,\n  title,\n  chat_id: resp.chat_id || $item(0).$node['Load State'].json.chat_id\n};\n\nstates[chatId].case_titles[caseId] = title;\n\nif (existing) {\n  existing.title = localCase.title;\n  existing.chat_id = localCase.chat_id;\n} else {\n  states[chatId].cases.unshift(localCase);\n}\n\ndata.states = states;\n\nreturn [{\n  json: {\n    chat_id: $item(0).$node['Load State'].json.chat_id,\n    case_id: caseId,\n    title,\n    message_text: `\u0414\u0435\u043b\u043e \u0441\u043e\u0437\u0434\u0430\u043d\u043e\\n\\n<b>${title}</b>\\nID: <code>${caseId}</code>`,\n    reply_markup_json: JSON.stringify({\n      inline_keyboard: [\n        [{ text: '\u0414\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u044b', callback_data: 'CASE_LIST_DOCS' }, { text: '\u0421\u043f\u0440\u043e\u0441\u0438\u0442\u044c \u043f\u043e \u0434\u0435\u043b\u0443', callback_data: 'CASE_ASK' }],\n        [{ text: '\u0414\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442', callback_data: 'CASE_ADD_DOC' }, { text: '\u0423\u0434\u0430\u043b\u0438\u0442\u044c \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442', callback_data: 'CASE_DELETE_DOC' }],\n        [{ text: '\u041d\u0430\u0437\u0430\u0434', callback_data: 'BACK_LIST_CASES' }, { text: '\u0413\u043b\u0430\u0432\u043d\u043e\u0435 \u043c\u0435\u043d\u044e', callback_data: 'BACK_MAIN' }]\n      ]\n    })\n  }\n}];"
      },
      "id": "3d26909f-1397-4764-929e-35c228d92a75",
      "name": "Save Created Case",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        48512,
        12176
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "=https://api.telegram.org/bot{{$env.TELEGRAM_BOT_TOKEN}}/sendMessage",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ {\"chat_id\": $json.chat_id, \"text\": $json.message_text, \"parse_mode\": \"HTML\", \"reply_markup\": JSON.parse($json.reply_markup_json)} }}",
        "options": {}
      },
      "id": "a5e43f0a-d37f-415b-ba2c-ca56ee1f309f",
      "name": "Created Case Message",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.3,
      "position": [
        48752,
        12176
      ]
    },
    {
      "parameters": {
        "url": "={{$json.api_base_url}}/cases?chat_id={{$json.chat_id}}",
        "options": {
          "redirect": {},
          "response": {
            "response": {
              "fullResponse": false,
              "neverError": true,
              "responseFormat": "json"
            }
          }
        }
      },
      "id": "b66c4be0-f6da-4d46-81dd-f52d8d696411",
      "name": "API List Cases",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.3,
      "position": [
        48272,
        12304
      ]
    },
    {
      "parameters": {
        "jsCode": "const payload = $json || {};\n\nlet apiCases = [];\nif (Array.isArray(payload.cases)) {\n  apiCases = payload.cases;\n} else if (Array.isArray(payload)) {\n  apiCases = payload;\n} else if (payload.case_id || payload.id) {\n  apiCases = [payload];\n}\n\nconst data = $getWorkflowStaticData('global');\nconst states = data.states || {};\nconst chatId = String($item(0).$node['Load State'].json.chat_id || 'unknown');\n\nstates[chatId] = states[chatId] || {};\nstates[chatId].cases = Array.isArray(states[chatId].cases) ? states[chatId].cases : [];\nstates[chatId].case_titles = states[chatId].case_titles || {};\n\nconst localCases = states[chatId].cases;\nconst caseTitles = states[chatId].case_titles || {};\n\nconst isGenericTitle = (value, id) => {\n  const title = String(value || '').trim();\n  if (!title) return true;\n  if (/^\u0414\u0435\u043b\u043e\\s+\\d+$/i.test(title)) return true;\n  if (/^Case\\s+\\d+$/i.test(title)) return true;\n  if (id && title === `\u0414\u0435\u043b\u043e ${String(id).replace(/^case_/, '')}`) return true;\n  return false;\n};\n\nconst mergedCases = apiCases.map(c => {\n  const id = String(c.case_id || c.id || '');\n  const local = localCases.find(x => String(x.case_id || x.id || '') === id) || {};\n  const apiTitle = String(c.title || '');\n  const localTitle = String(local.title || caseTitles[id] || '');\n\n  const chosenTitle = isGenericTitle(apiTitle, id)\n    ? (localTitle || apiTitle || `\u0414\u0435\u043b\u043e ${String(id).replace(/^case_/, '')}`)\n    : apiTitle;\n\n  if (chosenTitle) {\n    caseTitles[id] = chosenTitle;\n  }\n\n  return {\n    case_id: id,\n    title: chosenTitle || `\u0414\u0435\u043b\u043e ${String(id).replace(/^case_/, '')}`,\n    documents_count: Number(c.documents_count ?? local.documents_count ?? 0)\n  };\n}).filter(c => c.case_id);\n\nstates[chatId].case_titles = caseTitles;\nstates[chatId].cases = mergedCases;\nstates[chatId].pending_action = '';\ndata.states = states;\n\nif (!mergedCases.length) {\n  return [{\n    json: {\n      chat_id: $item(0).$node['Load State'].json.chat_id,\n      message_text: '\u0423 \u0432\u0430\u0441 \u043f\u043e\u043a\u0430 \u043d\u0435\u0442 \u0434\u0435\u043b\\n\\n\u041d\u0430\u0436\u043c\u0438\u0442\u0435 \u043a\u043d\u043e\u043f\u043a\u0443 \u0421\u043e\u0437\u0434\u0430\u0442\u044c \u0434\u0435\u043b\u043e',\n      reply_markup_json: JSON.stringify({\n        inline_keyboard: [\n          [{ text: '\u0421\u043e\u0437\u0434\u0430\u0442\u044c \u0434\u0435\u043b\u043e', callback_data: 'MENU_NEW_CASE' }, { text: '\u0413\u043b\u0430\u0432\u043d\u043e\u0435 \u043c\u0435\u043d\u044e', callback_data: 'BACK_MAIN' }]\n        ]\n      })\n    }\n  }];\n}\n\nconst activeCaseId = String(states[chatId].active_case_id || '');\nconst rows = mergedCases.map(c => {\n  const marker = c.case_id === activeCaseId ? '\u2022 ' : '';\n  const suffix = c.documents_count ? ` \u00b7 \u0434\u043e\u043a.: ${c.documents_count}` : '';\n  return [{\n    text: `${marker}${c.title}${suffix}`.slice(0, 60),\n    callback_data: `CASE_SELECT:${c.case_id}`\n  }];\n});\n\nrows.push([{ text: '\u041d\u0430\u0437\u0430\u0434', callback_data: 'BACK_MAIN' }]);\n\nreturn [{\n  json: {\n    chat_id: $item(0).$node['Load State'].json.chat_id,\n    message_text: '\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u0434\u0435\u043b\u043e',\n    reply_markup_json: JSON.stringify({ inline_keyboard: rows })\n  }\n}];"
      },
      "id": "fed34fbb-c73b-4cd0-8197-0888d5e5a64b",
      "name": "Prepare Cases Message",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        48512,
        12304
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "=https://api.telegram.org/bot{{$env.TELEGRAM_BOT_TOKEN}}/sendMessage",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ {\"chat_id\": $json.chat_id, \"text\": $json.message_text, \"parse_mode\": \"HTML\", \"reply_markup\": JSON.parse($json.reply_markup_json)} }}",
        "options": {}
      },
      "id": "2e1eb8cd-4161-4b92-90fd-aee6f9d6f93e",
      "name": "Send Cases Message",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.3,
      "position": [
        48752,
        12304
      ]
    },
    {
      "parameters": {
        "jsCode": "return [{ json: { ...$json } }];"
      },
      "id": "42102d53-6281-47a5-83da-00e73c2d996f",
      "name": "API Activate Case",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        48272,
        12416
      ]
    },
    {
      "parameters": {
        "jsCode": "const data = $getWorkflowStaticData('global');\nconst states = data.states || {};\nconst chatId = String($item(0).$node['Load State'].json.chat_id || 'unknown');\nconst caseId = $item(0).$node['Determine Route'].json.selected_case_id || '';\n\nstates[chatId] = states[chatId] || {};\nstates[chatId].active_case_id = caseId;\nstates[chatId].pending_action = '';\nstates[chatId].case_titles = states[chatId].case_titles || {};\n\nconst cases = Array.isArray(states[chatId].cases) ? states[chatId].cases : [];\nconst selected = cases.find(c => String(c.case_id || c.id || '') === String(caseId)) || {};\ndata.states = states;\n\nconst title = selected.title || states[chatId].case_titles[caseId] || `\u0414\u0435\u043b\u043e ${String(caseId).replace(/^case_/, '')}`;\n\nreturn [{\n  json: {\n    chat_id: $item(0).$node['Load State'].json.chat_id,\n    message_text: `\u0410\u043a\u0442\u0438\u0432\u043d\u043e\u0435 \u0434\u0435\u043b\u043e \u0432\u044b\u0431\u0440\u0430\u043d\u043e\\n\\n<b>${title}</b>\\nID: <code>${caseId}</code>`,\n    reply_markup_json: JSON.stringify({\n      inline_keyboard: [\n        [{ text: '\u0414\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u044b', callback_data: 'CASE_LIST_DOCS' }, { text: '\u0421\u043f\u0440\u043e\u0441\u0438\u0442\u044c \u043f\u043e \u0434\u0435\u043b\u0443', callback_data: 'CASE_ASK' }],\n        [{ text: '\u0414\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442', callback_data: 'CASE_ADD_DOC' }, { text: '\u0423\u0434\u0430\u043b\u0438\u0442\u044c \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442', callback_data: 'CASE_DELETE_DOC' }],\n        [{ text: '\u041d\u0430\u0437\u0430\u0434', callback_data: 'BACK_LIST_CASES' }, { text: '\u0413\u043b\u0430\u0432\u043d\u043e\u0435 \u043c\u0435\u043d\u044e', callback_data: 'BACK_MAIN' }]\n      ]\n    })\n  }\n}];"
      },
      "id": "fbcc3786-23c5-4204-bde4-3259475f50b7",
      "name": "Save Active Case",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        48512,
        12416
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "=https://api.telegram.org/bot{{$env.TELEGRAM_BOT_TOKEN}}/sendMessage",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ {\"chat_id\": $json.chat_id, \"text\": $json.message_text, \"parse_mode\": \"HTML\", \"reply_markup\": JSON.parse($json.reply_markup_json)} }}",
        "options": {}
      },
      "id": "55395765-5578-44df-9bb1-fba103cde01f",
      "name": "Selected Case Message",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.3,
      "position": [
        48752,
        12416
      ]
    },
    {
      "parameters": {
        "url": "={{$json.api_base_url}}/cases/{{$json.active_case_id}}/status?chat_id={{$json.chat_id}}",
        "options": {
          "response": {
            "response": {
              "neverError": true,
              "responseFormat": "json"
            }
          }
        }
      },
      "id": "10ca272b-1333-4995-aad2-3eea0a25b109",
      "name": "API Case Status",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.3,
      "position": [
        48272,
        12544
      ]
    },
    {
      "parameters": {
        "jsCode": "const data = $getWorkflowStaticData('global');\nconst states = data.states || {};\nconst chatId = String($item(0).$node['Load State'].json.chat_id || 'unknown');\nconst activeCaseId = String($item(0).$node['Load State'].json.active_case_id || '');\nstates[chatId] = states[chatId] || {};\nstates[chatId].pending_action = '';\nstates[chatId].case_titles = states[chatId].case_titles || {};\ndata.states = states;\nconst selected = (states[chatId]?.cases || []).find(c => String(c.case_id || c.id || '') === activeCaseId) || {};\nconst title = selected.title || states[chatId].case_titles[activeCaseId] || `\u0414\u0435\u043b\u043e ${String(activeCaseId).replace(/^case_/, '')}`;\n\nreturn [{\n  json: {\n    chat_id: $item(0).$node['Load State'].json.chat_id,\n    message_text: `\u0414\u0435\u043b\u043e\\n\\n<b>${title}</b>\\nID: <code>${activeCaseId}</code>`,\n    reply_markup_json: JSON.stringify({\n      inline_keyboard: [\n        [{ text: '\u0414\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u044b', callback_data: 'CASE_LIST_DOCS' }, { text: '\u0421\u043f\u0440\u043e\u0441\u0438\u0442\u044c \u043f\u043e \u0434\u0435\u043b\u0443', callback_data: 'CASE_ASK' }],\n        [{ text: '\u0414\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442', callback_data: 'CASE_ADD_DOC' }, { text: '\u0423\u0434\u0430\u043b\u0438\u0442\u044c \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442', callback_data: 'CASE_DELETE_DOC' }],\n        [{ text: '\u041d\u0430\u0437\u0430\u0434', callback_data: 'BACK_LIST_CASES' }, { text: '\u0413\u043b\u0430\u0432\u043d\u043e\u0435 \u043c\u0435\u043d\u044e', callback_data: 'BACK_MAIN' }]\n      ]\n    })\n  }\n}];"
      },
      "id": "039677e3-16e3-4f27-b176-b1fb9653d721",
      "name": "Prepare Status Message",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        48512,
        12544
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "=https://api.telegram.org/bot{{$env.TELEGRAM_BOT_TOKEN}}/sendMessage",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ {\"chat_id\": $json.chat_id, \"text\": $json.message_text, \"parse_mode\": \"HTML\", \"reply_markup\": JSON.parse($json.reply_markup_json)} }}",
        "options": {}
      },
      "id": "a503bc0a-bf42-4bce-b8e7-b7b0dc4ad4ed",
      "name": "Send Status Message",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.3,
      "position": [
        48752,
        12544
      ]
    },
    {
      "parameters": {
        "url": "={{$json.api_base_url}}/cases/{{$json.active_case_id}}?chat_id={{$json.chat_id}}",
        "options": {
          "response": {
            "response": {
              "neverError": true,
              "responseFormat": "json"
            }
          }
        }
      },
      "id": "a415273f-8624-48f1-abe4-311574665014",
      "name": "API Get Case Details",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.3,
      "position": [
        48272,
        12656
      ]
    },
    {
      "parameters": {
        "jsCode": "const details = $json || {};\nconst load = $item(0).$node['Load State'].json;\nconst data = $getWorkflowStaticData('global');\nconst states = data.states || {};\nconst chatId = String(load.chat_id || 'unknown');\nconst activeCaseId = String(load.active_case_id || details.case_id || '');\nstates[chatId] = states[chatId] || {};\nstates[chatId].case_titles = states[chatId].case_titles || {};\nstates[chatId].pending_action = '';\n\nconst localCase = (states[chatId].cases || []).find(c => String(c.case_id || c.id || '') === activeCaseId) || {};\nconst apiTitle = String(details.title || '');\nconst localTitle = String(localCase.title || states[chatId].case_titles[activeCaseId] || '');\n\nconst isGenericTitle = (value, id) => {\n  const title = String(value || '').trim();\n  if (!title) return true;\n  if (/^\u0414\u0435\u043b\u043e\\s+\\d+$/i.test(title)) return true;\n  if (/^Case\\s+\\d+$/i.test(title)) return true;\n  if (id && title === `\u0414\u0435\u043b\u043e ${String(id).replace(/^case_/, '')}`) return true;\n  return false;\n};\n\nconst resolvedTitle = isGenericTitle(apiTitle, activeCaseId)\n  ? (localTitle || `\u0414\u0435\u043b\u043e ${String(activeCaseId).replace(/^case_/, '')}`)\n  : apiTitle;\n\nif (resolvedTitle) {\n  states[chatId].case_titles[activeCaseId] = resolvedTitle;\n  if (localCase && typeof localCase === 'object') {\n    localCase.title = resolvedTitle;\n  }\n  data.states = states;\n}\n\nconst title = String(resolvedTitle || `\u0414\u0435\u043b\u043e ${String(activeCaseId).replace(/^case_/, '')}`);\nconst docs = Array.isArray(details.documents) ? details.documents : [];\n\nconst escapeHtml = (value) => String(value || '')\n  .replace(/&/g, '&amp;')\n  .replace(/</g, '&lt;')\n  .replace(/>/g, '&gt;');\n\nif (Number(details.status) === 404 || details.error === 'Not Found') {\n  return [{\n    json: {\n      chat_id: load.chat_id,\n      message_text: `\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u0437\u0430\u0433\u0440\u0443\u0437\u0438\u0442\u044c \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u044b \u0434\u0435\u043b\u0430\\n\\nID: ${escapeHtml(activeCaseId)}\\n\u041e\u0448\u0438\u0431\u043a\u0430 API: 404 Not Found`,\n      reply_markup_json: JSON.stringify({\n        inline_keyboard: [\n          [{ text: '\u0414\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442', callback_data: 'CASE_ADD_DOC' }, { text: '\u0423\u0434\u0430\u043b\u0438\u0442\u044c \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442', callback_data: 'CASE_DELETE_DOC' }],\n          [{ text: '\u0421\u043f\u0440\u043e\u0441\u0438\u0442\u044c \u043f\u043e \u0434\u0435\u043b\u0443', callback_data: 'CASE_ASK' }, { text: '\u0421\u043f\u0438\u0441\u043e\u043a \u0434\u0435\u043b', callback_data: 'CASE_LIST' }],\n          [{ text: '\u041d\u0430\u0437\u0430\u0434', callback_data: 'BACK_CASE_MENU' }, { text: '\u0413\u043b\u0430\u0432\u043d\u043e\u0435 \u043c\u0435\u043d\u044e', callback_data: 'BACK_MAIN' }]\n        ]\n      })\n    }\n  }];\n}\n\nlet docsBlock = '\u0414\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u043e\u0432 \u043f\u043e\u043a\u0430 \u043d\u0435\u0442.';\nif (docs.length) {\n  docsBlock = docs.map((doc, index) => {\n    const name = escapeHtml(doc.name || doc.filename || `\u0414\u043e\u043a\u0443\u043c\u0435\u043d\u0442 ${index + 1}`);\n    const docId = escapeHtml(doc.doc_id || doc.id || `doc_${index + 1}`);\n    const pages = doc.pages !== undefined && doc.pages !== null ? `, \u0441\u0442\u0440.: ${escapeHtml(doc.pages)}` : '';\n    return `${index + 1}. ${name}\\nID: ${docId}${pages}`;\n  }).join('\\n\\n');\n}\n\nreturn [{\n  json: {\n    chat_id: load.chat_id,\n    message_text: `\u0414\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u044b \u0434\u0435\u043b\u0430\\n\\n<b>${escapeHtml(title)}</b>\\nID: ${escapeHtml(activeCaseId)}\\n\\n${docsBlock}`,\n    reply_markup_json: JSON.stringify({\n      inline_keyboard: [\n        [{ text: '\u0414\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442', callback_data: 'CASE_ADD_DOC' }, { text: '\u0423\u0434\u0430\u043b\u0438\u0442\u044c \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442', callback_data: 'CASE_DELETE_DOC' }],\n        [{ text: '\u0421\u043f\u0440\u043e\u0441\u0438\u0442\u044c \u043f\u043e \u0434\u0435\u043b\u0443', callback_data: 'CASE_ASK' }, { text: '\u0421\u043f\u0438\u0441\u043e\u043a \u0434\u0435\u043b', callback_data: 'CASE_LIST' }],\n        [{ text: '\u041d\u0430\u0437\u0430\u0434', callback_data: 'BACK_CASE_MENU' }, { text: '\u0413\u043b\u0430\u0432\u043d\u043e\u0435 \u043c\u0435\u043d\u044e', callback_data: 'BACK_MAIN' }]\n      ]\n    })\n  }\n}];"
      },
      "id": "410ddf57-6736-4760-aa45-aee37ce497a6",
      "name": "Prepare Docs Message",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        48512,
        12656
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "=https://api.telegram.org/bot{{$env.TELEGRAM_BOT_TOKEN}}/sendMessage",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ {\"chat_id\": $json.chat_id, \"text\": $json.message_text, \"parse_mode\": \"HTML\", \"reply_markup\": JSON.parse($json.reply_markup_json)} }}",
        "options": {}
      },
      "id": "329e23a1-4c89-4779-ad30-fb2f6c0f1ab1",
      "name": "Send Docs Message",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.3,
      "position": [
        48752,
        12656
      ]
    },
    {
      "parameters": {
        "url": "={{$json.api_base_url}}/cases/{{$json.active_case_id}}?chat_id={{$json.chat_id}}",
        "options": {
          "response": {
            "response": {
              "neverError": true,
              "responseFormat": "json"
            }
          }
        }
      },
      "id": "6dd5541e-262e-4a52-b4e9-870e621c4ad2",
      "name": "API Get Case Details For Delete",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.3,
      "position": [
        48272,
        12784
      ]
    },
    {
      "parameters": {
        "jsCode": "const details = $json || {};\nconst load = $item(0).$node['Load State'].json;\nconst data = $getWorkflowStaticData('global');\nconst states = data.states || {};\nconst chatId = String(load.chat_id || 'unknown');\nconst activeCaseId = String(load.active_case_id || details.case_id || '');\nstates[chatId] = states[chatId] || {};\nstates[chatId].case_titles = states[chatId].case_titles || {};\nstates[chatId].pending_action = '';\n\nconst localCase = (states[chatId].cases || []).find(c => String(c.case_id || c.id || '') === activeCaseId) || {};\nconst apiTitle = String(details.title || '');\nconst localTitle = String(localCase.title || states[chatId].case_titles[activeCaseId] || '');\n\nconst isGenericTitle = (value, id) => {\n  const title = String(value || '').trim();\n  if (!title) return true;\n  if (/^\u0414\u0435\u043b\u043e\\s+\\d+$/i.test(title)) return true;\n  if (/^Case\\s+\\d+$/i.test(title)) return true;\n  if (id && title === `\u0414\u0435\u043b\u043e ${String(id).replace(/^case_/, '')}`) return true;\n  return false;\n};\n\nconst resolvedTitle = isGenericTitle(apiTitle, activeCaseId)\n  ? (localTitle || `\u0414\u0435\u043b\u043e ${String(activeCaseId).replace(/^case_/, '')}`)\n  : apiTitle;\n\nif (resolvedTitle) {\n  states[chatId].case_titles[activeCaseId] = resolvedTitle;\n  if (localCase && typeof localCase === 'object') {\n    localCase.title = resolvedTitle;\n  }\n  data.states = states;\n}\n\nconst title = String(resolvedTitle || `\u0414\u0435\u043b\u043e ${String(activeCaseId).replace(/^case_/, '')}`);\nconst docs = Array.isArray(details.documents) ? details.documents : [];\n\nconst escapeHtml = (value) => String(value || '')\n  .replace(/&/g, '&amp;')\n  .replace(/</g, '&lt;')\n  .replace(/>/g, '&gt;');\n\nif (Number(details.status) === 404 || details.error === 'Not Found') {\n  return [{\n    json: {\n      chat_id: load.chat_id,\n      message_text: `\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u0437\u0430\u0433\u0440\u0443\u0437\u0438\u0442\u044c \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u044b \u0434\u043b\u044f \u0443\u0434\u0430\u043b\u0435\u043d\u0438\u044f\\n\\nID: ${escapeHtml(activeCaseId)}\\n\u041e\u0448\u0438\u0431\u043a\u0430 API: 404 Not Found`,\n      reply_markup_json: JSON.stringify({\n        inline_keyboard: [\n          [{ text: '\u0414\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u044b', callback_data: 'CASE_LIST_DOCS' }, { text: '\u0421\u043f\u0440\u043e\u0441\u0438\u0442\u044c \u043f\u043e \u0434\u0435\u043b\u0443', callback_data: 'CASE_ASK' }],\n          [{ text: '\u041d\u0430\u0437\u0430\u0434', callback_data: 'BACK_CASE_MENU' }, { text: '\u0413\u043b\u0430\u0432\u043d\u043e\u0435 \u043c\u0435\u043d\u044e', callback_data: 'BACK_MAIN' }]\n        ]\n      })\n    }\n  }];\n}\n\nif (!docs.length) {\n  return [{\n    json: {\n      chat_id: load.chat_id,\n      message_text: `\u0412 \u0434\u0435\u043b\u0435 \u043f\u043e\u043a\u0430 \u043d\u0435\u0442 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u043e\u0432 \u0434\u043b\u044f \u0443\u0434\u0430\u043b\u0435\u043d\u0438\u044f\\n\\n<b>${escapeHtml(title)}</b>\\nID: ${escapeHtml(activeCaseId)}`,\n      reply_markup_json: JSON.stringify({\n        inline_keyboard: [\n          [{ text: '\u0414\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442', callback_data: 'CASE_ADD_DOC' }, { text: '\u0414\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u044b', callback_data: 'CASE_LIST_DOCS' }],\n          [{ text: '\u041d\u0430\u0437\u0430\u0434', callback_data: 'BACK_CASE_MENU' }, { text: '\u0413\u043b\u0430\u0432\u043d\u043e\u0435 \u043c\u0435\u043d\u044e', callback_data: 'BACK_MAIN' }]\n        ]\n      })\n    }\n  }];\n}\n\nconst buttons = docs.slice(0, 50).map((doc, index) => {\n  const docId = String(doc.doc_id || doc.id || '');\n  const name = String(doc.name || doc.filename || `\u0414\u043e\u043a\u0443\u043c\u0435\u043d\u0442 ${index + 1}`);\n  return [{\n    text: `\u0423\u0434\u0430\u043b\u0438\u0442\u044c: ${name}`.slice(0, 60),\n    callback_data: `DOC_DELETE:${docId}`\n  }];\n});\n\nbuttons.push([{ text: '\u0414\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u044b', callback_data: 'CASE_LIST_DOCS' }, { text: '\u0421\u043f\u0440\u043e\u0441\u0438\u0442\u044c \u043f\u043e \u0434\u0435\u043b\u0443', callback_data: 'CASE_ASK' }]);\nbuttons.push([{ text: '\u041d\u0430\u0437\u0430\u0434', callback_data: 'BACK_CASE_MENU' }, { text: '\u0413\u043b\u0430\u0432\u043d\u043e\u0435 \u043c\u0435\u043d\u044e', callback_data: 'BACK_MAIN' }]);\n\nreturn [{\n  json: {\n    chat_id: load.chat_id,\n    message_text: `\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442 \u0434\u043b\u044f \u0443\u0434\u0430\u043b\u0435\u043d\u0438\u044f\\n\\n<b>${escapeHtml(title)}</b>\\nID: ${escapeHtml(activeCaseId)}`,\n    reply_markup_json: JSON.stringify({ inline_keyboard: buttons })\n  }\n}];"
      },
      "id": "d0d01518-af93-4ce0-ba36-a581894b6815",
      "name": "Prepare Delete Buttons",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        48512,
        12784
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "=https://api.telegram.org/bot{{$env.TELEGRAM_BOT_TOKEN}}/sendMessage",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ {\"chat_id\": $json.chat_id, \"text\": $json.message_text, \"parse_mode\": \"HTML\", \"reply_markup\": JSON.parse($json.reply_markup_json)} }}",
        "options": {}
      },
      "id": "e5f7e141-d5c3-4564-8d40-bd1b63d27e4d",
      "name": "Send Delete Options",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.3,
      "position": [
        48752,
        12784
      ]
    },
    {
      "parameters": {
        "method": "DELETE",
        "url": "={{$json.api_base_url}}/cases/{{$json.active_case_id}}/documents/{{ String($json.selected_doc_id).replace('doc_', '') }}",
        "options": {
          "response": {
            "response": {
              "neverError": true,
              "responseFormat": "text"
            }
          }
        }
      },
      "id": "34e6c035-eb91-4e95-90a0-f46a33a7b754",
      "name": "API Delete Document",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.3,
      "position": [
        48272,
        12896
      ]
    },
    {
      "parameters": {
        "jsCode": "const buildRaw = $json.data ?? (typeof $json === 'string' ? $json : JSON.stringify($json));\nlet buildParsed = {};\ntry { buildParsed = typeof buildRaw === 'string' ? JSON.parse(buildRaw) : buildRaw; } catch (e) { buildParsed = {}; }\n\nconst prep = $item(0).$node['Prepare Delete Rebuild Request'].json;\nconst selectedDocId = String(prep.selected_doc_id || '');\n\nconst escapeHtml = (value) => String(value || '')\n  .replace(/&/g, '&amp;')\n  .replace(/</g, '&lt;')\n  .replace(/>/g, '&gt;');\n\nconst ok = !!prep.delete_ok;\n\nconst buildLooksBad = (() => {\n  const text = String(buildRaw || '').toLowerCase();\n  if (!text) return false;\n  return text.includes('\\\"detail\\\"') || text.includes('\\\"error\\\"') || text.includes('traceback') || text.includes('exception');\n})();\n\nlet message_text;\n\nif (ok) {\n  const buildNote = buildLooksBad\n    ? '\\n\\n\u0414\u043e\u043a\u0443\u043c\u0435\u043d\u0442 \u0443\u0434\u0430\u043b\u0451\u043d, \u043d\u043e \u043f\u0435\u0440\u0435\u0441\u0431\u043e\u0440\u043a\u0430 \u0438\u043d\u0434\u0435\u043a\u0441\u0430 \u0432\u0435\u0440\u043d\u0443\u043b\u0430 \u043d\u0435\u0441\u0442\u0430\u043d\u0434\u0430\u0440\u0442\u043d\u044b\u0439 \u043e\u0442\u0432\u0435\u0442. \u041f\u0440\u043e\u0432\u0435\u0440\u044c\u0442\u0435 RAG \u0441\u0435\u0440\u0432\u0438\u0441.'\n    : '\\n\\n\u041f\u0435\u0440\u0435\u0441\u0431\u043e\u0440\u043a\u0430 \u0438\u043d\u0434\u0435\u043a\u0441\u0430 \u0437\u0430\u043f\u0443\u0449\u0435\u043d\u0430.';\n  message_text = `\u0414\u043e\u043a\u0443\u043c\u0435\u043d\u0442 \u0443\u0434\u0430\u043b\u0451\u043d\\n\\nID \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430: ${escapeHtml(selectedDocId)}${buildNote}`;\n} else {\n  message_text = `\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u0443\u0434\u0430\u043b\u0438\u0442\u044c \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\\n\\nID \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430: ${escapeHtml(selectedDocId)}\\n\\n\u041e\u0442\u0432\u0435\u0442 API:\\n<code>${escapeHtml(String(prep.delete_raw || '').slice(0, 1000))}</code>`;\n}\n\nreturn [{\n  json: {\n    chat_id: prep.chat_id,\n    message_text,\n    reply_markup_json: JSON.stringify({\n      inline_keyboard: [\n        [{ text: '\u0414\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u044b', callback_data: 'CASE_LIST_DOCS' }, { text: '\u0421\u043f\u0440\u043e\u0441\u0438\u0442\u044c \u043f\u043e \u0434\u0435\u043b\u0443', callback_data: 'CASE_ASK' }],\n        [{ text: '\u0414\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442', callback_data: 'CASE_ADD_DOC' }, { text: '\u0423\u0434\u0430\u043b\u0438\u0442\u044c \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442', callback_data: 'CASE_DELETE_DOC' }],\n        [{ text: '\u041d\u0430\u0437\u0430\u0434', callback_data: 'BACK_CASE_MENU' }, { text: '\u0413\u043b\u0430\u0432\u043d\u043e\u0435 \u043c\u0435\u043d\u044e', callback_data: 'BACK_MAIN' }]\n      ]\n    })\n  }\n}];"
      },
      "id": "14022e35-ed94-4a36-ac79-9b146e4b2d85",
      "name": "Prepare Delete Result",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        48512,
        12896
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "=https://api.telegram.org/bot{{$env.TELEGRAM_BOT_TOKEN}}/sendMessage",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ {\"chat_id\": $json.chat_id, \"text\": $json.message_text, \"parse_mode\": \"HTML\", \"reply_markup\": JSON.parse($json.reply_markup_json)} }}",
        "options": {}
      },
      "id": "66ef9ee8-b26e-4933-935a-9dd29d5f1313",
      "name": "Send Delete Result",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.3,
      "position": [
        48752,
        12896
      ]
    },
    {
      "parameters": {
        "jsCode": "const data = $getWorkflowStaticData('global');\nconst states = data.states || {};\nconst chatId = String($json.chat_id || 'unknown');\nstates[chatId] = states[chatId] || {};\nstates[chatId].active_case_id = states[chatId].active_case_id || $json.active_case_id || '';\nstates[chatId].pending_action = 'waiting_document';\ndata.states = states;\nreturn [{ json: { chat_id: $json.chat_id, message_text: '\u0422\u0435\u043f\u0435\u0440\u044c \u043e\u0442\u043f\u0440\u0430\u0432\u044c\u0442\u0435 PDF, DOCX \u0438\u043b\u0438 TXT \u043e\u0434\u043d\u0438\u043c \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435\u043c', reply_markup_json: JSON.stringify({ inline_keyboard: [[{ text: '\u041d\u0430\u0437\u0430\u0434', callback_data: 'BACK_CASE_MENU' }]] }) } }];"
      },
      "id": "550a94c4-559c-479f-b677-67a3e4166515",
      "name": "Set Waiting Document",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        48272,
        13024
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "=https://api.telegram.org/bot{{$env.TELEGRAM_BOT_TOKEN}}/sendMessage",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ {\"chat_id\": $json.chat_id, \"text\": $json.message_text, \"parse_mode\": \"HTML\", \"reply_markup\": JSON.parse($json.reply_markup_json)} }}",
        "options": {}
      },
      "id": "04ba4b19-1e0c-43a5-8ae8-fd6b8839d4e0",
      "name": "Prompt Document Upload",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.3,
      "position": [
        48512,
        13024
      ]
    },
    {
      "parameters": {
        "url": "=https://api.telegram.org/bot{{$env.TELEGRAM_BOT_TOKEN}}/getFile?file_id={{$json.telegram_file_id}}",
        "options": {
          "response": {
            "response": {
              "neverError": true,
              "responseFormat": "json"
            }
          }
        }
      },
      "id": "24e2d495-7c3d-4dbb-9102-e9040b1aa49a",
      "name": "Telegram Get File",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.3,
      "position": [
        48272,
        13136
      ]
    },
    {
      "parameters": {
        "jsCode": "const result = $json.result || {};\nreturn [{ json: { ...$item(0).$node['Load State'].json, telegram_document_name: $item(0).$node['Determine Route'].json.telegram_document_name, telegram_mime_type: $item(0).$node['Determine Route'].json.telegram_mime_type, telegram_file_path: result.file_path || '', message_text: '' } }];"
      },
      "id": "89d4c8ec-f82b-4363-9a7c-4e82c2ddaead",
      "name": "Prepare Telegram File Path",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        48512,
        13136
      ]
    },
    {
      "parameters": {
        "url": "=https://api.telegram.org/file/bot{{$env.TELEGRAM_BOT_TOKEN}}/{{$json.telegram_file_path}}",
        "options": {
          "response": {
            "response": {
              "responseFormat": "file",
              "outputPropertyName": "telegram_file"
            }
          }
        }
      },
      "id": "a5912de2-52a7-48dd-80de-222459c63327",
      "name": "Download Telegram File",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.3,
      "position": [
        48752,
        13136
      ]
    },
    {
      "parameters": {
        "jsCode": "const items = $input.all();\nreturn items.map(item => {\n  item.binary = item.binary || {};\n  if (item.binary.telegram_file) {\n    item.binary.telegram_file.fileName = item.json.telegram_document_name || 'document.bin';\n    item.binary.telegram_file.mimeType = item.json.telegram_mime_type || item.binary.telegram_file.mimeType || 'application/octet-stream';\n  }\n  return item;\n});"
      },
      "id": "60c9ae31-51db-495c-ae57-f7e07c212778",
      "name": "Attach Binary Metadata",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        48992,
        13136
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "={{$json.api_base_url}}/cases/{{$json.active_case_id}}/documents",
        "sendBody": true,
        "contentType": "multipart-form-data",
        "bodyParameters": {
          "parameters": [
            {
              "parameterType": "formBinaryData",
              "name": "file",
              "inputDataFieldName": "telegram_file"
            }
          ]
        },
        "options": {
          "redirect": {},
          "response": {
            "response": {
              "fullResponse": false,
              "neverError": true,
              "responseFormat": "text",
              "outputPropertyName": "data"
            }
          }
        }
      },
      "id": "ad76670b-ecb8-479c-98b6-490c49220cb6",
      "name": "HTTP Upload Document",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.3,
      "position": [
        49232,
        13136
      ]
    },
    {
      "parameters": {
        "jsCode": "const buildRaw = $json.data ?? (typeof $json === 'string' ? $json : JSON.stringify($json));\nconst upload = $item(0).$node['Prepare Build Request'].json;\nconst uploadOk = !!upload.upload_ok;\n\nconst escapeHtml = (value) => String(value || '')\n  .replace(/&/g, '&amp;')\n  .replace(/</g, '&lt;')\n  .replace(/>/g, '&gt;');\n\nconst buildLooksBad = (() => {\n  const text = String(buildRaw || '').toLowerCase();\n  if (!text) return false;\n  return text.includes('\"detail\"') || text.includes('\"error\"') || text.includes('traceback') || text.includes('exception');\n})();\n\nlet text;\nif (!uploadOk) {\n  text = `\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u0442\u0432\u0435\u0440\u0434\u0438\u0442\u044c \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0443 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\\n\\n\u041e\u0442\u0432\u0435\u0442 API:\\n<code>${escapeHtml(String(upload.upload_raw || '').slice(0, 700))}</code>`;\n} else {\n  const buildNote = buildLooksBad\n    ? '\\n\\n\u0414\u043e\u043a\u0443\u043c\u0435\u043d\u0442 \u0441\u043e\u0445\u0440\u0430\u043d\u0451\u043d, \u043d\u043e \u043f\u0435\u0440\u0435\u0441\u0431\u043e\u0440\u043a\u0430 \u0438\u043d\u0434\u0435\u043a\u0441\u0430 \u0432\u0435\u0440\u043d\u0443\u043b\u0430 \u043d\u0435\u0441\u0442\u0430\u043d\u0434\u0430\u0440\u0442\u043d\u044b\u0439 \u043e\u0442\u0432\u0435\u0442. \u041f\u0440\u043e\u0432\u0435\u0440\u044c\u0442\u0435 RAG \u0441\u0435\u0440\u0432\u0438\u0441.'\n    : '\\n\\n\u041f\u0435\u0440\u0435\u0441\u0431\u043e\u0440\u043a\u0430 \u0438\u043d\u0434\u0435\u043a\u0441\u0430 \u0437\u0430\u043f\u0443\u0449\u0435\u043d\u0430.';\n  text = `\u0414\u043e\u043a\u0443\u043c\u0435\u043d\u0442 \u0437\u0430\u0433\u0440\u0443\u0436\u0435\u043d\\n\\n\u0424\u0430\u0439\u043b: ${escapeHtml(upload.file_name)}\\n\u0414\u0435\u043b\u043e: ${escapeHtml(upload.active_case_id)}\\nID \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430: ${escapeHtml(upload.upload_doc_id || '\u043d\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u043d\u043e')}${buildNote}`;\n}\n\nreturn [{\n  json: {\n    chat_id: upload.chat_id,\n    message_text: text,\n    reply_markup_json: JSON.stringify({\n      inline_keyboard: [\n        [{ text: '\u0414\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u044b', callback_data: 'CASE_LIST_DOCS' }, { text: '\u0421\u043f\u0440\u043e\u0441\u0438\u0442\u044c \u043f\u043e \u0434\u0435\u043b\u0443', callback_data: 'CASE_ASK' }],\n        [{ text: '\u041d\u0430\u0437\u0430\u0434', callback_data: 'BACK_CASE_MENU' }, { text: '\u0413\u043b\u0430\u0432\u043d\u043e\u0435 \u043c\u0435\u043d\u044e', callback_data: 'BACK_MAIN' }]\n      ]\n    })\n  }\n}];"
      },
      "id": "ff3c5000-822b-4d99-8bb6-7412f7b688ce",
      "name": "Finalize Document Upload",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        49472,
        13136
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "=https://api.telegram.org/bot{{$env.TELEGRAM_BOT_TOKEN}}/sendMessage",
        "sendBody": true,
        "bodyParameters": {
          "parameters": [
            {
              "name": "chat_id",
              "value": "={{$json.chat_id}}"
            },
            {
              "name": "text",
              "value": "={{$json.message_text}}"
            },
            {
              "name": "reply_markup",
              "value": "={{$json.reply_markup_json}}"
            }
          ]
        },
        "options": {}
      },
      "id": "4607adcd-bcb6-4848-bcef-c60eb3d3380b",
      "name": "Send Upload Result",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.3,
      "position": [
        49712,
        13136
      ]
    },
    {
      "parameters": {
        "jsCode": "const data = $getWorkflowStaticData('global');\nconst states = data.states || {};\nconst chatId = String($json.chat_id || 'unknown');\nstates[chatId] = states[chatId] || {};\nstates[chatId].pending_action = 'waiting_question';\ndata.states = states;\nreturn [{ json: { chat_id: $json.chat_id, message_text: '\u041d\u0430\u043f\u0438\u0448\u0438\u0442\u0435 \u0432\u043e\u043f\u0440\u043e\u0441 \u043f\u043e \u0434\u0435\u043b\u0443 \u043e\u0434\u043d\u0438\u043c \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435\u043c', reply_markup_json: JSON.stringify({ inline_keyboard: [[{ text: '\u041d\u0430\u0437\u0430\u0434', callback_data: 'BACK_CASE_MENU' }]] }) } }];"
      },
      "id": "694ac548-55b2-49dd-a0c9-4fc9ee548ffb",
      "name": "Set Waiting Question",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        48272,
        13264
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "=https://api.telegram.org/bot{{$env.TELEGRAM_BOT_TOKEN}}/sendMessage",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ {\"chat_id\": $json.chat_id, \"text\": $json.message_text, \"parse_mode\": \"HTML\", \"reply_markup\": JSON.parse($json.reply_markup_json)} }}",
        "options": {}
      },
      "id": "8e988ef7-c12f-4b04-abd6-2bcae238ef75",
      "name": "Prompt Ask Question",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.3,
      "position": [
        48512,
        13264
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "={{$json.rag_base_url}}/retrieve",
        "sendBody": true,
        "s

Credentials you'll need

Each integration node will prompt for credentials when you import. We strip credential IDs before publishing — you'll add your own.

Pro

For the full experience including quality scoring and batch install features for each workflow upgrade to Pro

How this works

This workflow delivers instant access to precise legal guidance via Telegram, empowering lawyers, legal researchers, and compliance officers to query regulations or case precedents without sifting through vast documents. It leverages a retrieval-augmented generation system to fetch and summarise relevant legal information from a secure knowledge base, ensuring responses are current and contextually accurate. The key step involves the Telegram trigger capturing user queries, which then routes through normalisation and state management to deliver tailored replies, integrating seamlessly with HTTP requests for real-time data pulls from sources like GitHub repositories.

Use this workflow for on-demand legal queries in client consultations or team collaborations where speed and reliability matter, such as verifying compliance updates. Avoid it for non-legal domains or when handling highly sensitive data requiring on-premise processing, as it relies on API-driven retrieval. Common variations include adapting the knowledge base for specific jurisdictions or adding multi-language support for international legal teams.

About this workflow

legal_rag_telegram_api_current_github_ready. Uses telegramTrigger, httpRequest. Event-driven trigger; 56 nodes.

Source: https://github.com/CrafticEA/legal-rag-telegram-bot/blob/01276b82dad2bf72edaae4425d726520945ff9e5/n8n/n8n_legal_rag_api.json — original creator credit. Request a take-down →

More AI & RAG workflows → · Browse all categories →

Related workflows

Workflows that share integrations, category, or trigger type with this one. All free to copy and import.

AI & RAG

Creators, designers, and developers exploring AI-powered image generation. Automation enthusiasts who want to integrate image creation into n8n workflows. Telegram bot builders looking to add visual A

Telegram Trigger, Airtable, HTTP Request +2
AI & RAG

[](https://www.linkedin.com/in/mosaab-yassir-lafrimi/)[](https://t.me/joevenner)

Telegram Trigger, HTTP Request, Redis +3
AI & RAG

Transcribe audio messages from Telegram using Google Gemini for free.

Telegram, Telegram Trigger, HTTP Request +3
AI & RAG

A sophisticated Telegram bot that provides AI-powered responses with conversation memory. This template demonstrates how to integrate any AI API service with Telegram, making it easy to swap between d

Telegram Trigger, Telegram, HTTP Request
AI & RAG

Template Description This description details the template's purpose, how it works, and its key features. You can copy and use it directly.

Telegram, HTTP Request, Postgres +1