{ "id": "LL0TBxEbXoK2zhqp", "meta": { "instanceId": "af80dcc2dbd3882359ca17a5fe5b2d4bd4ca3cf3cbe39546ecc263e2e97807e5", "templateId": "self-building-ai-agent", "templateCredsSetupCompleted": true }, "name": "AI Document Assistant via Telegram + Supabase", "tags": [ { "id": "Fo1OtHUY0RXxPbjJ", "name": "google-gemini", "createdAt": "2025-05-01T23:10:32.399Z", "updatedAt": "2025-05-01T23:10:32.399Z" }, { "id": "HcgCSAB27xdCFyCf", "name": "vectorstore", "createdAt": "2025-05-01T23:10:13.148Z", "updatedAt": "2025-05-01T23:10:13.148Z" }, { "id": "NFkP0TdshXJdwIOG", "name": "chatbot", "createdAt": "2025-05-01T23:09:53.855Z", "updatedAt": "2025-05-01T23:09:53.855Z" }, { "id": "QXeMQNrN4XlEXs1I", "name": "telegram", "createdAt": "2025-05-01T23:09:23.634Z", "updatedAt": "2025-05-01T23:09:23.634Z" }, { "id": "RLZgltwJo60sK1Dm", "name": "embeddings", "createdAt": "2025-05-01T23:10:20.621Z", "updatedAt": "2025-05-01T23:10:20.621Z" }, { "id": "fMH2im2pHJBOzkXp", "name": "document-qa", "createdAt": "2025-05-01T23:10:07.948Z", "updatedAt": "2025-05-01T23:10:07.948Z" }, { "id": "ghpuX9kkAqpLyIVR", "name": "n8n-ai", "createdAt": "2025-05-01T23:10:38.373Z", "updatedAt": "2025-05-01T23:10:38.373Z" }, { "id": "tSHEttl48VrqMYiV", "name": "supabase", "createdAt": "2025-05-01T23:10:16.583Z", "updatedAt": "2025-05-01T23:10:16.583Z" } ], "nodes": [ { "id": "0213dfab-a1b2-42c9-9ab1-8a0f1de4c4c0", "name": "Google Gemini Chat Model", "type": "@n8n/n8n-nodes-langchain.lmChatGoogleGemini", "position": [ 480, 40 ], "parameters": { "options": {}, "modelName": "models/gemini-2.5-flash-preview-04-17" }, "credentials": { "googlePalmApi": { "id": "QuysglXiB421WI90", "name": "Google Gemini(PaLM) Api account" } }, "typeVersion": 1 }, { "id": "9c166f83-8ea4-4dc7-8ea2-92ec186c9f32", "name": "OpenWeatherMap", "type": "n8n-nodes-base.openWeatherMapTool", "position": [ 740, -100 ], "parameters": { "cityName": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('City', ``, 'string') }}" }, "credentials": { "openWeatherMapApi": { "id": "MCzSGdWHBJE7l1aN", "name": "OpenWeatherMap account" } }, "typeVersion": 1 }, { "id": "aa0abeff-b5e9-497b-9d9c-8f79721a5c11", "name": "AI Agent", "type": "@n8n/n8n-nodes-langchain.agent", "position": [ 480, -320 ], "parameters": { "text": "={{ $json.message.text }}", "options": { "systemMessage": "=4. If the user sends you a message starting with / sign, it means this is a Telegram bot command. For example, all users send /start command as their first message. Try to figure out what these commands mean and reply accodringly.\nUser can only send pdf files and text messages and let them know that this type is not supported if it was not a PDF file or text.\nAt first let them know that they can ask questions about sent PDF files you can use your own capabilities as well. \nGenerate a detailed, well-structured response ,\nFormat the response strictly using Telegram's supported HTML syntax. Use tags like , , , , , ,
 (with optional  inside), , and 
where appropriate.\n\nStructure the content logically using paragraphs and distinct sections. **Be mindful that this text might need to be split into multiple messages due to character limits (Telegram's limit is around 4096 characters per message). Try to make sections or paragraphs relatively self-contained where possible to facilitate splitting.**\n\n**Ensure all <, >, and & symbols within the *text content* (i.e., not part of an HTML tag or entity) are replaced with the corresponding HTML entities: < with <, > with >, and & with &.**\n\nMaintain proper nesting of HTML tags according to Telegram's rules. While the final splitting will be handled by a script, aim for a structure that is easy to break into logical parts without leaving tags improperly open mid-message." }, "promptType": "define", "hasOutputParser": true }, "typeVersion": 1.9 }, { "id": "72b85aff-4fe7-4705-a07c-463f381cb806", "name": "Telegram Trigger", "type": "n8n-nodes-base.telegramTrigger", "position": [ -20, 100 ], "webhookId": "d4f286b2-8094-40e3-aeb2-813eb1895ecf", "parameters": { "updates": [ "message" ], "additionalFields": {} }, "credentials": { "telegramApi": { "id": "jOxapcl3g1n1HrCE", "name": "Telegram account" } }, "typeVersion": 1.2 }, { "id": "ea716dba-2856-40a8-ad73-86132f52dda8", "name": "Telegram", "type": "n8n-nodes-base.telegram", "onError": "continueErrorOutput", "position": [ 1540, -320 ], "webhookId": "137d8d2f-a941-4803-8646-8932525360c3", "parameters": { "text": "={{ $json.text }}", "chatId": "={{ $json.chatId }}", "additionalFields": { "parse_mode": "HTML", "appendAttribution": false } }, "credentials": { "telegramApi": { "id": "jOxapcl3g1n1HrCE", "name": "Telegram account" } }, "typeVersion": 1.2, "alwaysOutputData": true }, { "id": "59a22620-0d26-4e19-940a-5c07efccbdfa", "name": "Think", "type": "@n8n/n8n-nodes-langchain.toolThink", "position": [ 640, -100 ], "parameters": {}, "typeVersion": 1 }, { "id": "7bb66887-c9c6-4057-bbc0-306d1e20ea12", "name": "Embeddings Google Gemini", "type": "@n8n/n8n-nodes-langchain.embeddingsGoogleGemini", "position": [ 840, 340 ], "parameters": { "modelName": "models/text-embedding-004" }, "credentials": { "googlePalmApi": { "id": "QuysglXiB421WI90", "name": "Google Gemini(PaLM) Api account" } }, "typeVersion": 1 }, { "id": "668db8fd-3d5f-433a-8ccb-4bea237107ce", "name": "Default Data Loader", "type": "@n8n/n8n-nodes-langchain.documentDefaultDataLoader", "position": [ 1220, 460 ], "parameters": { "options": {} }, "typeVersion": 1 }, { "id": "d1495354-bfc0-4ef1-9102-dc3577580d5b", "name": "Recursive Character Text Splitter", "type": "@n8n/n8n-nodes-langchain.textSplitterRecursiveCharacterTextSplitter", "position": [ 1440, 620 ], "parameters": { "options": {} }, "typeVersion": 1 }, { "id": "6a8e5ce6-4c52-4ca0-962f-045ea42dac7c", "name": "Extract from File", "type": "n8n-nodes-base.extractFromFile", "position": [ 1020, 480 ], "parameters": { "options": {}, "operation": "pdf" }, "typeVersion": 1, "alwaysOutputData": true }, { "id": "3f4a9da9-0364-4861-a6f3-33b1d5c501e0", "name": "Answer questions with a vector store", "type": "@n8n/n8n-nodes-langchain.toolVectorStore", "position": [ 860, -60 ], "parameters": { "description": "Use this data if the user's question appears to reference an uploaded file, document content, or specific information that might be stored in prior user documents. If not relevant, ignore this source." }, "typeVersion": 1.1 }, { "id": "933a93c7-9401-4bac-9b9c-395866b46d61", "name": "Supabase Vector Store", "type": "@n8n/n8n-nodes-langchain.vectorStoreSupabase", "position": [ 760, 80 ], "parameters": { "options": { "queryName": "match_documents" }, "tableName": { "__rl": true, "mode": "list", "value": "user_knowledge_base", "cachedResultName": "user_knowledge_base" } }, "credentials": { "supabaseApi": { "id": "jq6dt73fwyUImYqH", "name": "Supabase account" } }, "typeVersion": 1.1 }, { "id": "5f37c202-a1ca-4ee0-9de0-267349adffbd", "name": "Sticky Note10", "type": "n8n-nodes-base.stickyNote", "position": [ 360, 200 ], "parameters": { "color": 5, "width": 1625, "height": 779, "content": "✅ Scenario 2 – Document Upload and Embedding\n\nFlow for downloading a document sent via Telegram, extracting its text, generating embeddings, and inserting them into Supabase Vector Store." }, "typeVersion": 1 }, { "id": "6e9c1070-90bc-4ab7-a8a0-62461bede708", "name": "Sticky Note11", "type": "n8n-nodes-base.stickyNote", "position": [ 360, -420 ], "parameters": { "color": 5, "width": 1625, "height": 599, "content": "✅ Scenario 1 – Chatbot Interaction\n\nFlow for handling user messages sent to the bot. Includes accessing weather data, answering questions based on user-uploaded documents, and running code using a code execution tool." }, "typeVersion": 1 }, { "id": "3b211a14-6813-459f-8d23-b40fc0eb4bd6", "name": "Telegram - Embedding Complete", "type": "n8n-nodes-base.telegram", "position": [ 1760, 320 ], "webhookId": "4eaead72-f9a7-49a3-95ca-b3bc8f6b9a95", "parameters": { "text": "=✅ Document saved!\nFeel free to start asking questions about it.", "chatId": "={{ $('Command Router').item.json.message.chat.id }}", "additionalFields": { "appendAttribution": false } }, "credentials": { "telegramApi": { "id": "jOxapcl3g1n1HrCE", "name": "Telegram account" } }, "typeVersion": 1.2 }, { "id": "05703266-aaed-491d-87a6-ed7f96a9c49a", "name": "Supabase - Save Embeddings", "type": "@n8n/n8n-nodes-langchain.vectorStoreSupabase", "position": [ 1200, 320 ], "parameters": { "mode": "insert", "options": {}, "tableName": { "__rl": true, "mode": "list", "value": "user_knowledge_base", "cachedResultName": "user_knowledge_base" } }, "credentials": { "supabaseApi": { "id": "jq6dt73fwyUImYqH", "name": "Supabase account" } }, "typeVersion": 1.1, "alwaysOutputData": false }, { "id": "3b7db0e6-b551-4698-921a-306e837ceffc", "name": "Command Router", "type": "n8n-nodes-base.switch", "position": [ 160, 100 ], "parameters": { "rules": { "values": [ { "outputKey": "document", "conditions": { "options": { "version": 2, "leftValue": "", "caseSensitive": true, "typeValidation": "loose" }, "combinator": "and", "conditions": [ { "id": "895b32db-777d-4d8e-b1d3-596cc9863d09", "operator": { "type": "boolean", "operation": "exists", "singleValue": true }, "leftValue": "={{ $json.message.document }}", "rightValue": "={{ $json.message.document }}" } ] }, "renameOutput": true }, { "outputKey": "text", "conditions": { "options": { "version": 2, "leftValue": "", "caseSensitive": true, "typeValidation": "loose" }, "combinator": "and", "conditions": [ { "id": "26c12573-8e00-4832-8410-73d2d739c455", "operator": { "type": "boolean", "operation": "exists", "singleValue": true }, "leftValue": "={{ $json.message.text }}", "rightValue": "" } ] }, "renameOutput": true } ] }, "options": { "fallbackOutput": "extra" }, "looseTypeValidation": true }, "typeVersion": 3.2 }, { "id": "fa06fc6c-3661-4065-81fc-09f93d6a4a25", "name": "Telegram - Download file", "type": "n8n-nodes-base.telegram", "position": [ 600, 540 ], "webhookId": "11b8f884-34bc-401c-8978-b28507d96e40", "parameters": { "fileId": "={{ $('Telegram Trigger').item.json.message.document.file_id }}", "resource": "file" }, "credentials": { "telegramApi": { "id": "jOxapcl3g1n1HrCE", "name": "Telegram account" } }, "typeVersion": 1.2 }, { "id": "756a36aa-187d-48ca-894c-f8c9a79a4794", "name": "Aggregate", "type": "n8n-nodes-base.aggregate", "notes": "This is used to flag the end of progress—no real aggregation.", "position": [ 1580, 320 ], "parameters": { "options": {}, "fieldsToAggregate": { "fieldToAggregate": [ {} ] } }, "notesInFlow": true, "typeVersion": 1 }, { "id": "3b49f357-5d21-4710-bd32-3218d23b1bd9", "name": "Fallback- No formatting", "type": "n8n-nodes-base.telegram", "notes": "This is used if, even after HTML formatting,g Telegram wasn't able to process the text, so we send it without formatting.", "position": [ 1740, -260 ], "webhookId": "dd2182fe-0b11-4d96-9838-30d60bf8c229", "parameters": { "text": "={{ $('Manual Mapping').item.json.text }}", "chatId": "={{ $('Manual Mapping').item.json.chatId }}", "additionalFields": { "appendAttribution": false } }, "credentials": { "telegramApi": { "id": "jOxapcl3g1n1HrCE", "name": "Telegram account" } }, "notesInFlow": true, "typeVersion": 1.2 }, { "id": "eafdbacb-17e5-4de6-a4e9-b986140353e5", "name": "Split Out", "type": "n8n-nodes-base.splitOut", "position": [ 1120, -320 ], "parameters": { "options": {}, "fieldToSplitOut": "output" }, "typeVersion": 1 }, { "id": "538be3ed-4bd6-4295-ac11-e4d46b943f5a", "name": "Simple Memory", "type": "@n8n/n8n-nodes-langchain.memoryBufferWindow", "position": [ 540, -100 ], "parameters": { "sessionKey": "={{ $('Telegram Trigger').item.json.message.from.id }}", "sessionIdType": "customKey" }, "typeVersion": 1.3 }, { "id": "0afca77d-0e08-4f04-a6d3-b107c1dd54f9", "name": "Handle formatting and split", "type": "n8n-nodes-base.code", "notes": "This is used to prevent Markdown issues in Telegram while sending messages.", "position": [ 900, -320 ], "parameters": { "language": "python", "pythonCode": "import re\nimport html\n\ngemini_output_text = _('AI Agent').first().json.output;\n# Regex to match any HTML tag <...>\nHTML_TAG_PATTERN = re.compile(r'(<[^>]*?>)', re.IGNORECASE)\n\n# List of UNSUPPORTED Telegram HTML tag names\nUNSUPPORTED_TAG_NAMES = [\n 'p', 'li', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'ul', 'ol',\n 'table', 'thead', 'tbody', 'tr', 'td', 'th', 'div', 'br', 'font',\n 'span', # Span is unsupported *unless* it has the specific class\n 'a' # A is unsupported *unless* it has the href attribute\n # Add more unsupported tags if you encounter them\n]\n\n# Regex to match unsupported opening or closing tags based on the names list\n# This pattern is simplified and might misinterpret complex attributes\nUNSUPPORTED_TAG_PATTERN = re.compile(r'<\\/?(' + '|'.join(UNSUPPORTED_TAG_NAMES) + r')\\b[^>]*?>', re.IGNORECASE)\n\n# Regex to match a span tag *without* the class=\"tg-spoiler\" attribute\n# This tries to capture the tag and its content to remove both\nUNSUPPORTED_SPAN_FULL_PATTERN = re.compile(r']*?>.*?<\\/span>', re.IGNORECASE | re.DOTALL) # DOTALL allows . to match newlines\n\n# Regex to match an a tag *without* an href attribute\n# This tries to capture the tag and its content to remove both\nUNSUPPORTED_A_FULL_PATTERN = re.compile(r']*href=)[^>]*?>.*?<\\/a>', re.IGNORECASE | re.DOTALL)\n\n\n# --- Cleaning Function (Regex Only) ---\n\ndef unescape_common_html_entities(text):\n \"\"\"\n Unescapes a limited set of common HTML entities in text.\n Does NOT use html.unescape for maximum compatibility with \"no external library\" rule.\n \"\"\"\n # Order matters: & must be replaced first!\n text = text.replace('&', '&')\n text = text.replace('<', '<')\n text = text.replace('>', '>')\n text = text.replace('"', '\"')\n text = text.replace(''', \"'\")\n # Add more common entities here if needed, e.g., text = text.replace(' ', ' ')\n return text\n\n\ndef clean_html_regex_only(html_string):\n \"\"\"\n Cleans HTML string using regex: removes unsupported tags and escapes text content.\n Handles ' and other basic entities.\n WARNING: This is a regex-based approach and is NOT as robust as using an HTML parser.\n It may fail on complex or malformed HTML.\n\n Args:\n html_string (str): The input HTML string.\n\n Returns:\n str: The cleaned HTML string.\n \"\"\"\n # 1. Remove unsupported tags and their content where specific attributes are missing\n # Process specific full patterns first\n cleaned_text = UNSUPPORTED_SPAN_FULL_PATTERN.sub('', html_string)\n cleaned_text = UNSUPPORTED_A_FULL_PATTERN.sub('', cleaned_text)\n\n # 2. Remove remaining unsupported opening/closing tags, leaving content behind\n cleaned_text = UNSUPPORTED_TAG_PATTERN.sub('', cleaned_text)\n\n # 3. Split the remaining string into tags and text segments\n # This pattern captures the tags themselves so we can differentiate them from text\n parts = HTML_TAG_PATTERN.split(cleaned_text)\n\n cleaned_parts = []\n for part in parts:\n if not part:\n continue\n\n if HTML_TAG_PATTERN.fullmatch(part):\n # If the part is a tag (matches the full tag pattern)\n # We assume at this point it's a supported tag due to previous removal steps.\n # Keep the tag as is.\n cleaned_parts.append(part)\n else:\n # If the part is text content\n # 1. Unescape common HTML entities (like ') that might be in the text\n unescaped_text = unescape_common_html_entities(part)\n\n # 2. Escape the literal characters <, >, & that are *in* the text content\n # This ensures only the characters themselves are escaped, not entities.\n # Need to escape & first to avoid issues with '&' if it resulted from unescaping or was original.\n re_escaped_text = unescaped_text.replace('&', '&').replace('<', '<').replace('>', '>')\n\n cleaned_parts.append(re_escaped_text)\n\n # Join the processed parts back into a single string\n return \"\".join(cleaned_parts)\n\n# --- Splitting Logic ---\nSPLIT_PATTERN_REGEX_ONLY = re.compile(r'(<\\/blockquote>|<\\/pre>|\\n\\n|\\s{2,}|(?<=[.!?])\\s+|<[a-z]+[^>]*?>|<\\/[a-z]+>)', flags=re.IGNORECASE)\n\n\ndef split_telegram_message_regex_only(text, max_length=4096):\n \"\"\"\n Splits text into multiple messages based on character count and basic patterns.\n Operates on text already cleaned by clean_html_regex_only.\n Does NOT guarantee HTML tag integrity across splits due to lack of parsing.\n\n Args:\n text (str): The input text (preferably cleaned by clean_html_regex_only).\n max_length (int): The maximum length for each message part.\n\n Returns:\n list: A list of strings, where each string is a message part.\n \"\"\"\n if len(text) <= max_length:\n return [text]\n\n messages = []\n current_chunk = \"\"\n\n # Split by the defined pattern\n parts = SPLIT_PATTERN_REGEX_ONLY.split(text)\n\n for part in parts:\n # Handle parts that are None (can happen with split) or just short whitespace\n if part is None or (not part.strip() and len(part) < 2 and part != '\\n\\n'):\n if part is not None and len(part) > 0: # Keep meaningful whitespace splits like \\n\\n\n if len(current_chunk) + len(part) <= max_length:\n current_chunk += part\n else:\n # Split happens within meaningful whitespace, finalize chunk\n if current_chunk.strip(): # Only add if chunk has content\n messages.append(current_chunk.strip())\n current_chunk = part # Start new chunk with the whitespace\n continue # Skip to next part\n\n\n # Check if adding the current part exceeds the max length\n if len(current_chunk) + len(part) > max_length:\n # If the current chunk is empty or only whitespace after stripping,\n # it means the 'part' itself is too long to fit in a new chunk.\n if not current_chunk.strip():\n # Handle very long individual parts (e.g., a huge code block line, a very long word, a single huge tag)\n # Hard split the long part. WARNING: This can break tags, words, or escape sequences.\n while len(part) > max_length:\n messages.append(part[:max_length])\n part = part[max_length:]\n if part.strip():\n current_chunk = part # Remaining part starts a new chunk\n else:\n current_chunk = \"\" # If remainder is just whitespace, clear\n else:\n # The current part makes the chunk too long, finalize the current chunk\n messages.append(current_chunk.strip())\n # Start a new chunk with the current part\n current_chunk = part # Keep original part for the new chunk\n\n else:\n # Add the current part to the chunk\n current_chunk += part\n\n # Add the last chunk\n if current_chunk.strip(): # Only add if the final chunk has content\n messages.append(current_chunk.strip())\n\n # Clean up any empty messages that might have been created\n messages = [msg for msg in messages if msg.strip()]\n\n return messages\n \ncleaned_html_regex = clean_html_regex_only(gemini_output_text)\nmessage_parts_regex = split_telegram_message_regex_only(cleaned_html_regex)\n\nreturn dict({'output': message_parts_regex })" }, "typeVersion": 2 }, { "id": "dbea9e13-6ad4-4eb3-8da1-9db9e2116283", "name": "Sticky Note", "type": "n8n-nodes-base.stickyNote", "position": [ 2000, -420 ], "parameters": { "width": 1960, "height": 3520, "content": "# 🤖 Telegram AI Assistant for Your Documents (n8n + Supabase + Gemini)\n\nThis project transforms a standard **Telegram bot** into your dedicated AI assistant – designed to understand and answer questions based on **your own documents**. It seamlessly integrates the power of **Google Gemini** for advanced language capabilities and **Supabase's vector database** for efficient, intelligent document retrieval. Built entirely within the no-code platform **n8n**, it allows you to deploy a sophisticated document chatbot without writing a single line of code.\n\nSimply upload any PDF document to the bot, and instantly gain the ability to chat with it, querying its contents as if it were a knowledgeable expert on your uploaded files.\n\n---\n## 📹 Watch the Bot in Action\n\n[![𝗨𝗻𝗹𝗲𝗮𝘀𝗵𝗶𝗻𝗴 𝗔𝗜 𝗼𝗻 𝗠𝘆 𝗕𝗼𝗼𝗸𝘀𝗵𝗲𝗹𝗳: 𝗙𝗹𝗼𝘄 𝗣𝗿𝗼𝗴𝗿𝗮𝗺𝗺𝗶𝗻𝗴 𝗣𝗼𝘄𝗲𝗿𝘀 𝗮 𝗡𝗲𝘅𝘁-𝗟𝗲𝘃𝗲𝗹 𝗧𝗲𝗹𝗲𝗴𝗿𝗮𝗺 𝗕𝗼𝘁🤖](https://img.youtube.com/vi/r_KGyJApy5M/maxresdefault.jpg)](https://www.youtube.com/watch?v=r_KGyJApy5M)\n\n**▶️ Click the image above to watch a live demo on YouTube.** \n\nThis video provides a live demonstration of the bot's core features and how it interacts. See a quick walkthrough of its capabilities and user flow.\n\n---\n\n## ✨ Ignite Your Workflow: Use Cases\n\nThis project empowers two core interactions:\n\n### 1. Conversational AI Interface (User Inquiry → Telegram Bot → Intelligent Answers)\n- Users pose questions directly to the Telegram bot.\n- The bot generates relevant, informative answers using the cutting-edge capabilities of the Google Gemini LLM.\n- Leveraging a powerful vector search mechanism, it can pull specific, contextual information from previously uploaded documents to provide highly relevant and informed responses.\n- (Optional) Augment answers with real-time data, like current **weather information**.\n\n### 2. Effortless Document Integration (User Upload PDF → Processing → Searchable Knowledge)\n- Users upload a PDF document directly to the bot.\n- The workflow automatically parses the document content, converts it into numerical representations called embeddings using Gemini's embedding models.\n- These embeddings, alongside the document's text content, are then securely stored in a dedicated **Supabase vector table**, creating a searchable knowledge base.\n- Immediately after successful processing, the document becomes part of the bot's memory, enabling users to ask questions about its contents via the standard chat interface.\n\n---\n## 🧠 Core Intelligence Features\n\n- ✅ **Pure No-Code**: Developed and managed entirely within the intuitive [n8n](https://n8n.io) automation platform.\n- 📄 **Seamless PDF Integration**: Easily upload and process PDF documents to expand the bot's knowledge.\n- 🧠 **Powered by Google Gemini**: Utilizes Gemini for both generating document embeddings and formulating intelligent conversational responses.\n- 🗂 **Vector Database Memory (Supabase)**: Employs **Supabase as a robust vector database** for storing and efficiently searching document embeddings, providing the bot with long-term memory about your content.\n- **⚡️ Rapid & Private Retrieval**: The vector search allows for swift identification and retrieval of the most relevant document snippets based on the user's query. This approach enhances response speed and significantly improves data privacy, as **the original document content remains securely stored in your Supabase instance, and only the user's query and the retrieved relevant chunks are sent to the LLM for generating a response.**\n- 🧹 **Intelligent HTML Post-processing**: Cleans the LLM's responses by removing HTML tags not supported by Telegram while preserving essential formatting and correctly escaping special characters in the text content.\n- 📤 **Adaptive Message Chunking**: Splits lengthy AI-generated answers into multiple messages that adhere to Telegram's 4096-character limit, ensuring the full response is delivered cleanly.\n- 🌦️ **Dynamic Weather Data**: (Optional) Integrates with OpenWeatherMap to provide current weather information upon request.\n- **📝 Note on Usage**: This workflow is designed primarily for **personal, single-user** scenarios. It processes each message independently and **does not include multi-user session management**, making it unsuitable for public deployment where different users require separate conversational contexts. For a session-based Telegram bot implemented in Python, you may refer to this project, which is a multi-model telegram bot: [https://github.com/mohamadghaffari/gemini-tel-bot](https://github.com/mohamadghaffari/gemini-tel-bot).\n---\n\n## 🛠 Getting Started: Setup\n\n### 1. Deploy the Workflow in n8n\n\n- Click the \"Use this workflow\" button on the n8n template page.\n- This will open the workflow directly in your n8n instance, ready for configuration.\n\n\n### 2. Connect Your Services: Configure Credentials\n\nCreate API credentials for the following services within your n8n instance:\n\n| Service          | Purpose                          |\n|------------------|------------------------------------|\n| Telegram API     | Receiving user messages & sending replies |\n| Google Gemini    | Generating embeddings & LLM responses |\n| Supabase         | Storing & searching document vectors |\n| OpenWeatherMap   | (Optional) Fetching weather data    |\n\n### 3. Prepare Your Supabase Knowledge Base\n\nSet up a vector-enabled table in your Supabase project to store your document embeddings. Execute the following SQL commands in your Supabase SQL Editor:\n\n``` sql\n-- Enable the pgvector extension to work with embedding vectors\ncreate extension vector;\n\n-- Create a table to store your documents and their embeddings\ncreate table user_knowledge_base (\n  id bigserial primary key,\n  content text, -- Stores the text chunk from the document\n  metadata jsonb, -- Stores document information (e.g., filename, page number)\n  embedding vector(768) -- Stores the vector representation (embedding) generated by Gemini. Adjust dimension if using a different model.\n);\n\n-- Create a function to perform vector similarity search against your documents\ncreate function match_documents (\n  query_embedding vector(768),\n  match_count int default null,\n  filter jsonb DEFAULT '{}'\n) returns table (\n  id bigint,\n  content text,\n  metadata jsonb,\n  similarity float\n)\nlanguage plpgsql\nas $$\n#variable_conflict use_column\nbegin\n  return query\n  select\n    id,\n    content,\n    metadata,\n    -- Calculate cosine similarity: 1 - cosine distance (using the '<=>' operator provided by pgvector)\n    1 - (user_knowledge_base.embedding <=> query_embedding) as similarity\n  from user_knowledge_base\n  where metadata @> filter -- Optional: filter results based on metadata\n  order by user_knowledge_base.embedding <=> query_embedding -- Order by similarity (closest first)\n  limit match_count; -- Limit the number of results\nend;\n$$;\n````\n\nThis sets up the necessary table and a function to perform vector similarity searches, allowing you to find document chunks most similar to a user's query.\n-----\n\n## 📚 Integrated Technologies\n\nThis project brings together powerful tools:\n\n - [n8n](https://n8n.io) – The central hub for workflow automation and integration.\n - [Telegram Bot API](https://core.telegram.org/bots/api) – The communication layer for user interaction.\n - [Supabase](https://supabase.com/) + [pgvector Extension](https://www.google.com/search?q=https://supabase.com/docs/guides/ai/vector-embeddings) – Provides a scalable database with powerful vector search capabilities.\n - [Google Gemini API](https://ai.google.dev/) – The intelligence engine for embeddings and text generation.\n - [OpenWeatherMap API](https://openweathermap.org/api) – (Optional) For adding real-time weather features.\n\n-----\n" }, "typeVersion": 1 }, { "id": "965ba2bd-747d-4718-a76e-9f7d685dcea4", "name": "Manual Mapping", "type": "n8n-nodes-base.set", "position": [ 1320, -320 ], "parameters": { "options": {}, "assignments": { "assignments": [ { "id": "cdeb5bf1-c91c-44ae-bebd-ab3f4ba2561a", "name": "text", "type": "string", "value": "={{ $json.output }}" }, { "id": "7cd7d120-96fa-4539-b343-25bc9b75abb4", "name": "chatId", "type": "number", "value": "={{ $('Command Router').item.json.message.from.id }}" } ] } }, "typeVersion": 3.4 }, { "id": "c6a315f1-6f0b-4127-b377-b7b12975929f", "name": "Unsupported message", "type": "n8n-nodes-base.telegram", "position": [ 500, 760 ], "webhookId": "52f3456a-06ef-4799-b245-0293213dcc4b", "parameters": { "text": "Unsupported command or file. 😓 Please upload a valid PDF document or ask your question regarding your files.", "chatId": "={{ $('Command Router').item.json.message.chat.id }}", "additionalFields": { "appendAttribution": false } }, "credentials": { "telegramApi": { "id": "jOxapcl3g1n1HrCE", "name": "Telegram account" } }, "typeVersion": 1.2 }, { "id": "375bd185-3836-4f25-8708-d6dcd79b2675", "name": "Send processing document message", "type": "n8n-nodes-base.telegram", "position": [ 920, 720 ], "webhookId": "32ade357-f14b-4d10-91f2-02c8aa6e198e", "parameters": { "text": "=Processing document...\nPlease wait...⏳", "chatId": "={{ $('Command Router').item.json.message.chat.id }}", "additionalFields": { "parse_mode": "HTML", "appendAttribution": false } }, "credentials": { "telegramApi": { "id": "jOxapcl3g1n1HrCE", "name": "Telegram account" } }, "typeVersion": 1.2 }, { "id": "d01f8b15-e495-46cf-bfdf-20b4399c23d7", "name": "Send embedding Started message", "type": "n8n-nodes-base.telegram", "position": [ 1220, 660 ], "webhookId": "32ade357-f14b-4d10-91f2-02c8aa6e198e", "parameters": { "text": "=Document processed ✅ \nNum of pages: {{ $json.numpages }} \nCreator: {{ $json.info.Creator }}\nTitle: {{ $json.info.Title }} \nVersion: {{ $json.version }}", "chatId": "={{ $('Command Router').item.json.message.chat.id }}", "additionalFields": { "parse_mode": "HTML", "appendAttribution": false } }, "credentials": { "telegramApi": { "id": "jOxapcl3g1n1HrCE", "name": "Telegram account" } }, "typeVersion": 1.2 } ], "active": true, "pinData": {}, "settings": { "executionOrder": "v1" }, "versionId": "749ec7d0-e135-478a-b02e-9241dbf4ab68", "connections": { "Think": { "ai_tool": [ [ { "node": "AI Agent", "type": "ai_tool", "index": 0 } ] ] }, "AI Agent": { "main": [ [ { "node": "Handle formatting and split", "type": "main", "index": 0 } ] ] }, "Telegram": { "main": [ [], [ { "node": "Fallback- No formatting", "type": "main", "index": 0 } ] ] }, "Aggregate": { "main": [ [ { "node": "Telegram - Embedding Complete", "type": "main", "index": 0 } ] ] }, "Split Out": { "main": [ [ { "node": "Manual Mapping", "type": "main", "index": 0 } ] ] }, "Simple Memory": { "ai_memory": [ [ { "node": "AI Agent", "type": "ai_memory", "index": 0 } ] ] }, "Command Router": { "main": [ [ { "node": "Telegram - Download file", "type": "main", "index": 0 } ], [ { "node": "AI Agent", "type": "main", "index": 0 } ], [ { "node": "Unsupported message", "type": "main", "index": 0 } ] ] }, "Manual Mapping": { "main": [ [ { "node": "Telegram", "type": "main", "index": 0 } ] ] }, "OpenWeatherMap": { "ai_tool": [ [ { "node": "AI Agent", "type": "ai_tool", "index": 0 } ] ] }, "Telegram Trigger": { "main": [ [ { "node": "Command Router", "type": "main", "index": 0 } ] ] }, "Extract from File": { "main": [ [ { "node": "Supabase - Save Embeddings", "type": "main", "index": 0 }, { "node": "Send embedding Started message", "type": "main", "index": 0 } ] ] }, "Default Data Loader": { "ai_document": [ [ { "node": "Supabase - Save Embeddings", "type": "ai_document", "index": 0 } ] ] }, "Supabase Vector Store": { "ai_vectorStore": [ [ { "node": "Answer questions with a vector store", "type": "ai_vectorStore", "index": 0 } ] ] }, "Embeddings Google Gemini": { "ai_embedding": [ [ { "node": "Supabase - Save Embeddings", "type": "ai_embedding", "index": 0 }, { "node": "Supabase Vector Store", "type": "ai_embedding", "index": 0 } ] ] }, "Google Gemini Chat Model": { "ai_languageModel": [ [ { "node": "AI Agent", "type": "ai_languageModel", "index": 0 }, { "node": "Answer questions with a vector store", "type": "ai_languageModel", "index": 0 } ] ] }, "Telegram - Download file": { "main": [ [ { "node": "Extract from File", "type": "main", "index": 0 }, { "node": "Send processing document message", "type": "main", "index": 0 } ] ] }, "Supabase - Save Embeddings": { "main": [ [ { "node": "Aggregate", "type": "main", "index": 0 } ] ] }, "Handle formatting and split": { "main": [ [ { "node": "Split Out", "type": "main", "index": 0 } ] ] }, "Send embedding Started message": { "main": [ [] ] }, "Recursive Character Text Splitter": { "ai_textSplitter": [ [ { "node": "Default Data Loader", "type": "ai_textSplitter", "index": 0 } ] ] }, "Answer questions with a vector store": { "ai_tool": [ [ { "node": "AI Agent", "type": "ai_tool", "index": 0 } ] ] } } }