Skip to content

Instantly share code, notes, and snippets.

@philnash
Created November 13, 2025 05:49
Show Gist options
  • Select an option

  • Save philnash/3d665cedee01dbc759d8f16f31283224 to your computer and use it in GitHub Desktop.

Select an option

Save philnash/3d665cedee01dbc759d8f16f31283224 to your computer and use it in GitHub Desktop.
A Langflow Newsletter Helper with Bookmarklet
javascript: (function () {
const endpoint = new URL(
"http://localhost:7860/api/v1/run/YOUR_FLOW_ID"
);
const body = {
input_value: globalThis.location.href,
input_type: "text",
};
fetch(endpoint, {
method: "POST",
headers: {
"Content-Type": "application/json",
"x-api-key": "YOUR_LANGFLOW_API_KEY",
},
body: JSON.stringify(body),
})
.then((response) => {
if (!response.ok) throw new Error("Failed to bookmark");
return response.json();
})
.then(() => {
showMessage("Bookmark saved!", "success");
})
.catch((err) => {
showMessage("Error saving bookmark: " + err.message, "error");
});
function showMessage(msg, type) {
const div = globalThis.document.createElement("div");
div.textContent = msg;
div.style.position = "fixed";
div.style.top = "10px";
div.style.right = "10px";
div.style.padding = "10px 20px";
div.style.background = type === "success" ? "#4caf50" : "#f44336";
div.style.color = "#fff";
div.style.zIndex = 9999;
div.style.borderRadius = "4px";
globalThis.document.body.appendChild(div);
setTimeout(() => {
div.remove();
}, 5000);
}
})();
{
"data": {
"edges": [
{
"animated": false,
"className": "",
"data": {
"sourceHandle": {
"dataType": "TextInput",
"id": "TextInput-1pAQo",
"name": "text",
"output_types": [
"Message"
]
},
"targetHandle": {
"fieldName": "url_input",
"id": "APIRequest-inb27",
"inputTypes": [
"Message"
],
"type": "str"
}
},
"id": "xy-edge__TextInput-1pAQo{œdataTypeœ:œTextInputœ,œidœ:œTextInput-1pAQoœ,œnameœ:œtextœ,œoutput_typesœ:[œMessageœ]}-APIRequest-inb27{œfieldNameœ:œurl_inputœ,œidœ:œAPIRequest-inb27œ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}",
"selected": false,
"source": "TextInput-1pAQo",
"sourceHandle": "{œdataTypeœ:œTextInputœ,œidœ:œTextInput-1pAQoœ,œnameœ:œtextœ,œoutput_typesœ:[œMessageœ]}",
"target": "APIRequest-inb27",
"targetHandle": "{œfieldNameœ:œurl_inputœ,œidœ:œAPIRequest-inb27œ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}"
},
{
"animated": false,
"className": "",
"data": {
"sourceHandle": {
"dataType": "ParserComponent",
"id": "ParserComponent-O4WyQ",
"name": "parsed_text",
"output_types": [
"Message"
]
},
"targetHandle": {
"fieldName": "input_value",
"id": "LanguageModelComponent-cxlCl",
"inputTypes": [
"Message"
],
"type": "str"
}
},
"id": "xy-edge__ParserComponent-O4WyQ{œdataTypeœ:œParserComponentœ,œidœ:œParserComponent-O4WyQœ,œnameœ:œparsed_textœ,œoutput_typesœ:[œMessageœ]}-LanguageModelComponent-cxlCl{œfieldNameœ:œinput_valueœ,œidœ:œLanguageModelComponent-cxlClœ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}",
"selected": false,
"source": "ParserComponent-O4WyQ",
"sourceHandle": "{œdataTypeœ:œParserComponentœ,œidœ:œParserComponent-O4WyQœ,œnameœ:œparsed_textœ,œoutput_typesœ:[œMessageœ]}",
"target": "LanguageModelComponent-cxlCl",
"targetHandle": "{œfieldNameœ:œinput_valueœ,œidœ:œLanguageModelComponent-cxlClœ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}"
},
{
"animated": false,
"className": "",
"data": {
"sourceHandle": {
"dataType": "LanguageModelComponent",
"id": "LanguageModelComponent-cxlCl",
"name": "text_output",
"output_types": [
"Message"
]
},
"targetHandle": {
"fieldName": "content",
"id": "Prompt Template-HJD20",
"inputTypes": [
"Message"
],
"type": "str"
}
},
"id": "xy-edge__LanguageModelComponent-cxlCl{œdataTypeœ:œLanguageModelComponentœ,œidœ:œLanguageModelComponent-cxlClœ,œnameœ:œtext_outputœ,œoutput_typesœ:[œMessageœ]}-Prompt Template-HJD20{œfieldNameœ:œcontentœ,œidœ:œPrompt Template-HJD20œ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}",
"selected": false,
"source": "LanguageModelComponent-cxlCl",
"sourceHandle": "{œdataTypeœ:œLanguageModelComponentœ,œidœ:œLanguageModelComponent-cxlClœ,œnameœ:œtext_outputœ,œoutput_typesœ:[œMessageœ]}",
"target": "Prompt Template-HJD20",
"targetHandle": "{œfieldNameœ:œcontentœ,œidœ:œPrompt Template-HJD20œ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}"
},
{
"animated": false,
"className": "",
"data": {
"sourceHandle": {
"dataType": "TextInput",
"id": "TextInput-1pAQo",
"name": "text",
"output_types": [
"Message"
]
},
"targetHandle": {
"fieldName": "url",
"id": "Prompt Template-HJD20",
"inputTypes": [
"Message"
],
"type": "str"
}
},
"id": "xy-edge__TextInput-1pAQo{œdataTypeœ:œTextInputœ,œidœ:œTextInput-1pAQoœ,œnameœ:œtextœ,œoutput_typesœ:[œMessageœ]}-Prompt Template-HJD20{œfieldNameœ:œurlœ,œidœ:œPrompt Template-HJD20œ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}",
"selected": false,
"source": "TextInput-1pAQo",
"sourceHandle": "{œdataTypeœ:œTextInputœ,œidœ:œTextInput-1pAQoœ,œnameœ:œtextœ,œoutput_typesœ:[œMessageœ]}",
"target": "Prompt Template-HJD20",
"targetHandle": "{œfieldNameœ:œurlœ,œidœ:œPrompt Template-HJD20œ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}"
},
{
"animated": false,
"className": "",
"data": {
"sourceHandle": {
"dataType": "Prompt Template",
"id": "Prompt Template-HJD20",
"name": "prompt",
"output_types": [
"Message"
]
},
"targetHandle": {
"fieldName": "markdown_text",
"id": "AddContentToPage-bZnM0",
"inputTypes": [
"Message"
],
"type": "str"
}
},
"id": "xy-edge__Prompt Template-HJD20{œdataTypeœ:œPrompt Templateœ,œidœ:œPrompt Template-HJD20œ,œnameœ:œpromptœ,œoutput_typesœ:[œMessageœ]}-AddContentToPage-bZnM0{œfieldNameœ:œmarkdown_textœ,œidœ:œAddContentToPage-bZnM0œ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}",
"selected": false,
"source": "Prompt Template-HJD20",
"sourceHandle": "{œdataTypeœ:œPrompt Templateœ,œidœ:œPrompt Template-HJD20œ,œnameœ:œpromptœ,œoutput_typesœ:[œMessageœ]}",
"target": "AddContentToPage-bZnM0",
"targetHandle": "{œfieldNameœ:œmarkdown_textœ,œidœ:œAddContentToPage-bZnM0œ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}"
},
{
"animated": false,
"className": "",
"data": {
"sourceHandle": {
"dataType": "APIRequest",
"id": "APIRequest-inb27",
"name": "data",
"output_types": [
"Data"
]
},
"targetHandle": {
"fieldName": "input_data",
"id": "ParserComponent-O4WyQ",
"inputTypes": [
"DataFrame",
"Data"
],
"type": "other"
}
},
"id": "xy-edge__APIRequest-inb27{œdataTypeœ:œAPIRequestœ,œidœ:œAPIRequest-inb27œ,œnameœ:œdataœ,œoutput_typesœ:[œDataœ]}-ParserComponent-O4WyQ{œfieldNameœ:œinput_dataœ,œidœ:œParserComponent-O4WyQœ,œinputTypesœ:[œDataFrameœ,œDataœ],œtypeœ:œotherœ}",
"selected": false,
"source": "APIRequest-inb27",
"sourceHandle": "{œdataTypeœ:œAPIRequestœ,œidœ:œAPIRequest-inb27œ,œnameœ:œdataœ,œoutput_typesœ:[œDataœ]}",
"target": "ParserComponent-O4WyQ",
"targetHandle": "{œfieldNameœ:œinput_dataœ,œidœ:œParserComponent-O4WyQœ,œinputTypesœ:[œDataFrameœ,œDataœ],œtypeœ:œotherœ}"
}
],
"nodes": [
{
"data": {
"id": "AddContentToPage-bZnM0",
"node": {
"base_classes": [
"Data",
"Tool"
],
"beta": false,
"category": "Notion",
"conditional_paths": [],
"custom_fields": {},
"description": "Convert markdown text to Notion blocks and append them to a Notion page.",
"display_name": "Add Content to Page ",
"documentation": "https://developers.notion.com/reference/patch-block-children",
"edited": false,
"field_order": [
"markdown_text",
"block_id",
"notion_secret"
],
"frozen": false,
"icon": "NotionDirectoryLoader",
"key": "AddContentToPage",
"legacy": false,
"lf_version": "1.6.5",
"metadata": {},
"minimized": false,
"output_types": [],
"outputs": [
{
"allows_loop": false,
"cache": true,
"display_name": "Data",
"group_outputs": false,
"method": "run_model",
"name": "api_run_model",
"selected": "Data",
"tool_mode": true,
"types": [
"Data"
],
"value": "__UNDEFINED__"
},
{
"allows_loop": false,
"cache": true,
"display_name": "Tool",
"group_outputs": false,
"method": "build_tool",
"name": "api_build_tool",
"tool_mode": true,
"types": [
"Tool"
],
"value": "__UNDEFINED__"
}
],
"pinned": false,
"score": 2.220446049250313e-16,
"template": {
"_type": "Component",
"block_id": {
"_input_type": "StrInput",
"advanced": false,
"display_name": "Page/Block ID",
"dynamic": false,
"info": "The ID of the page/block to add the content.",
"list": false,
"list_add_label": "Add More",
"load_from_db": false,
"name": "block_id",
"placeholder": "",
"required": false,
"show": true,
"title_case": false,
"tool_mode": false,
"trace_as_metadata": true,
"type": "str",
"value": ""
},
"code": {
"advanced": true,
"dynamic": true,
"fileTypes": [],
"file_path": "",
"info": "",
"list": false,
"load_from_db": false,
"multiline": true,
"name": "code",
"password": false,
"placeholder": "",
"required": true,
"show": true,
"title_case": false,
"type": "code",
"value": "import json\nfrom typing import Any\n\nimport requests\nfrom bs4 import BeautifulSoup\nfrom langchain.tools import StructuredTool\nfrom markdown import markdown\nfrom pydantic import BaseModel, Field\n\nfrom langflow.base.langchain_utilities.model import LCToolComponent\nfrom langflow.field_typing import Tool\nfrom langflow.inputs.inputs import MultilineInput, SecretStrInput, StrInput\nfrom langflow.logging.logger import logger\nfrom langflow.schema.data import Data\n\nMIN_ROWS_IN_TABLE = 3\n\n\nclass AddContentToPage(LCToolComponent):\n display_name: str = \"Add Content to Page \"\n description: str = \"Convert markdown text to Notion blocks and append them to a Notion page.\"\n documentation: str = \"https://developers.notion.com/reference/patch-block-children\"\n icon = \"NotionDirectoryLoader\"\n\n inputs = [\n MultilineInput(\n name=\"markdown_text\",\n display_name=\"Markdown Text\",\n info=\"The markdown text to convert to Notion blocks.\",\n ),\n StrInput(\n name=\"block_id\",\n display_name=\"Page/Block ID\",\n info=\"The ID of the page/block to add the content.\",\n ),\n SecretStrInput(\n name=\"notion_secret\",\n display_name=\"Notion Secret\",\n info=\"The Notion integration token.\",\n required=True,\n ),\n ]\n\n class AddContentToPageSchema(BaseModel):\n markdown_text: str = Field(..., description=\"The markdown text to convert to Notion blocks.\")\n block_id: str = Field(..., description=\"The ID of the page/block to add the content.\")\n\n def run_model(self) -> Data:\n result = self._add_content_to_page(self.markdown_text, self.block_id)\n return Data(data=result, text=json.dumps(result))\n\n def build_tool(self) -> Tool:\n return StructuredTool.from_function(\n name=\"add_content_to_notion_page\",\n description=\"Convert markdown text to Notion blocks and append them to a Notion page.\",\n func=self._add_content_to_page,\n args_schema=self.AddContentToPageSchema,\n )\n\n def _add_content_to_page(self, markdown_text: str, block_id: str) -> dict[str, Any] | str:\n try:\n html_text = markdown(markdown_text)\n soup = BeautifulSoup(html_text, \"html.parser\")\n blocks = self.process_node(soup)\n\n url = f\"https://api.notion.com/v1/blocks/{block_id}/children\"\n headers = {\n \"Authorization\": f\"Bearer {self.notion_secret}\",\n \"Content-Type\": \"application/json\",\n \"Notion-Version\": \"2022-06-28\",\n }\n\n data = {\n \"children\": blocks,\n }\n\n response = requests.patch(url, headers=headers, json=data, timeout=10)\n response.raise_for_status()\n\n return response.json()\n except requests.exceptions.RequestException as e:\n error_message = f\"Error: Failed to add content to Notion page. {e}\"\n if hasattr(e, \"response\") and e.response is not None:\n error_message += f\" Status code: {e.response.status_code}, Response: {e.response.text}\"\n return error_message\n except Exception as e: # noqa: BLE001\n logger.debug(\"Error adding content to Notion page\", exc_info=True)\n return f\"Error: An unexpected error occurred while adding content to Notion page. {e}\"\n\n def process_node(self, node):\n blocks = []\n if isinstance(node, str):\n text = node.strip()\n if text:\n if text.startswith(\"#\"):\n heading_level = text.count(\"#\", 0, 6)\n heading_text = text[heading_level:].strip()\n if heading_level in range(3):\n blocks.append(self.create_block(f\"heading_{heading_level + 1}\", heading_text))\n else:\n blocks.append(self.create_block(\"paragraph\", text))\n elif node.name == \"h1\":\n blocks.append(self.create_block(\"heading_1\", node.get_text(strip=True)))\n elif node.name == \"h2\":\n blocks.append(self.create_block(\"heading_2\", node.get_text(strip=True)))\n elif node.name == \"h3\":\n blocks.append(self.create_block(\"heading_3\", node.get_text(strip=True)))\n elif node.name == \"p\":\n code_node = node.find(\"code\")\n if code_node:\n code_text = code_node.get_text()\n language, code = self.extract_language_and_code(code_text)\n blocks.append(self.create_block(\"code\", code, language=language))\n elif self.is_table(str(node)):\n blocks.extend(self.process_table(node))\n else:\n blocks.append(self.create_block(\"paragraph\", node.get_text(strip=True)))\n elif node.name == \"ul\":\n blocks.extend(self.process_list(node, \"bulleted_list_item\"))\n elif node.name == \"ol\":\n blocks.extend(self.process_list(node, \"numbered_list_item\"))\n elif node.name == \"blockquote\":\n blocks.append(self.create_block(\"quote\", node.get_text(strip=True)))\n elif node.name == \"hr\":\n blocks.append(self.create_block(\"divider\", \"\"))\n elif node.name == \"img\":\n blocks.append(self.create_block(\"image\", \"\", image_url=node.get(\"src\")))\n elif node.name == \"a\":\n blocks.append(self.create_block(\"bookmark\", node.get_text(strip=True), link_url=node.get(\"href\")))\n elif node.name == \"table\":\n blocks.extend(self.process_table(node))\n\n for child in node.children:\n if isinstance(child, str):\n continue\n blocks.extend(self.process_node(child))\n\n return blocks\n\n def extract_language_and_code(self, code_text):\n lines = code_text.split(\"\\n\")\n language = lines[0].strip()\n code = \"\\n\".join(lines[1:]).strip()\n return language, code\n\n def is_code_block(self, text):\n return text.startswith(\"```\")\n\n def extract_code_block(self, text):\n lines = text.split(\"\\n\")\n language = lines[0].strip(\"`\").strip()\n code = \"\\n\".join(lines[1:]).strip(\"`\").strip()\n return language, code\n\n def is_table(self, text):\n rows = text.split(\"\\n\")\n if len(rows) < MIN_ROWS_IN_TABLE:\n return False\n\n has_separator = False\n for i, row in enumerate(rows):\n if \"|\" in row:\n cells = [cell.strip() for cell in row.split(\"|\")]\n cells = [cell for cell in cells if cell] # Remove empty cells\n if i == 1 and all(set(cell) <= set(\"-|\") for cell in cells):\n has_separator = True\n elif not cells:\n return False\n\n return has_separator\n\n def process_list(self, node, list_type):\n blocks = []\n for item in node.find_all(\"li\"):\n item_text = item.get_text(strip=True)\n checked = item_text.startswith(\"[x]\")\n is_checklist = item_text.startswith(\"[ ]\") or checked\n\n if is_checklist:\n item_text = item_text.replace(\"[x]\", \"\").replace(\"[ ]\", \"\").strip()\n blocks.append(self.create_block(\"to_do\", item_text, checked=checked))\n else:\n blocks.append(self.create_block(list_type, item_text))\n return blocks\n\n def process_table(self, node):\n blocks = []\n header_row = node.find(\"thead\").find(\"tr\") if node.find(\"thead\") else None\n body_rows = node.find(\"tbody\").find_all(\"tr\") if node.find(\"tbody\") else []\n\n if header_row or body_rows:\n table_width = max(\n len(header_row.find_all([\"th\", \"td\"])) if header_row else 0,\n *(len(row.find_all([\"th\", \"td\"])) for row in body_rows),\n )\n\n table_block = self.create_block(\"table\", \"\", table_width=table_width, has_column_header=bool(header_row))\n blocks.append(table_block)\n\n if header_row:\n header_cells = [cell.get_text(strip=True) for cell in header_row.find_all([\"th\", \"td\"])]\n header_row_block = self.create_block(\"table_row\", header_cells)\n blocks.append(header_row_block)\n\n for row in body_rows:\n cells = [cell.get_text(strip=True) for cell in row.find_all([\"th\", \"td\"])]\n row_block = self.create_block(\"table_row\", cells)\n blocks.append(row_block)\n\n return blocks\n\n def create_block(self, block_type: str, content: str, **kwargs) -> dict[str, Any]:\n block: dict[str, Any] = {\n \"object\": \"block\",\n \"type\": block_type,\n block_type: {},\n }\n\n if block_type in {\n \"paragraph\",\n \"heading_1\",\n \"heading_2\",\n \"heading_3\",\n \"bulleted_list_item\",\n \"numbered_list_item\",\n \"quote\",\n }:\n block[block_type][\"rich_text\"] = [\n {\n \"type\": \"text\",\n \"text\": {\n \"content\": content,\n },\n }\n ]\n elif block_type == \"to_do\":\n block[block_type][\"rich_text\"] = [\n {\n \"type\": \"text\",\n \"text\": {\n \"content\": content,\n },\n }\n ]\n block[block_type][\"checked\"] = kwargs.get(\"checked\", False)\n elif block_type == \"code\":\n block[block_type][\"rich_text\"] = [\n {\n \"type\": \"text\",\n \"text\": {\n \"content\": content,\n },\n }\n ]\n block[block_type][\"language\"] = kwargs.get(\"language\", \"plain text\")\n elif block_type == \"image\":\n block[block_type] = {\"type\": \"external\", \"external\": {\"url\": kwargs.get(\"image_url\", \"\")}}\n elif block_type == \"divider\":\n pass\n elif block_type == \"bookmark\":\n block[block_type][\"url\"] = kwargs.get(\"link_url\", \"\")\n elif block_type == \"table\":\n block[block_type][\"table_width\"] = kwargs.get(\"table_width\", 0)\n block[block_type][\"has_column_header\"] = kwargs.get(\"has_column_header\", False)\n block[block_type][\"has_row_header\"] = kwargs.get(\"has_row_header\", False)\n elif block_type == \"table_row\":\n block[block_type][\"cells\"] = [[{\"type\": \"text\", \"text\": {\"content\": cell}} for cell in content]]\n\n return block\n"
},
"markdown_text": {
"_input_type": "MultilineInput",
"advanced": false,
"copy_field": false,
"display_name": "Markdown Text",
"dynamic": false,
"info": "The markdown text to convert to Notion blocks.",
"input_types": [
"Message"
],
"list": false,
"list_add_label": "Add More",
"load_from_db": false,
"multiline": true,
"name": "markdown_text",
"placeholder": "",
"required": false,
"show": true,
"title_case": false,
"tool_mode": false,
"trace_as_input": true,
"trace_as_metadata": true,
"type": "str",
"value": ""
},
"notion_secret": {
"_input_type": "SecretStrInput",
"advanced": false,
"display_name": "Notion Secret",
"dynamic": false,
"info": "The Notion integration token.",
"input_types": [],
"load_from_db": true,
"name": "notion_secret",
"password": true,
"placeholder": "",
"required": true,
"show": true,
"title_case": false,
"type": "str",
"value": ""
}
},
"tool_mode": false
},
"selected_output": "api_run_model",
"showNode": true,
"type": "AddContentToPage"
},
"dragging": false,
"id": "AddContentToPage-bZnM0",
"measured": {
"height": 385,
"width": 320
},
"position": {
"x": 1112.6989953660343,
"y": 34.28205263224681
},
"selected": true,
"type": "genericNode"
},
{
"data": {
"id": "TextInput-1pAQo",
"node": {
"base_classes": [
"Message"
],
"beta": false,
"conditional_paths": [],
"custom_fields": {},
"description": "Get user text inputs.",
"display_name": "Text Input",
"documentation": "https://docs.langflow.org/components-io#text-input",
"edited": false,
"field_order": [
"input_value"
],
"frozen": false,
"icon": "type",
"legacy": false,
"lf_version": "1.6.5",
"metadata": {},
"minimized": false,
"output_types": [],
"outputs": [
{
"allows_loop": false,
"cache": true,
"display_name": "Output Text",
"group_outputs": false,
"method": "text_response",
"name": "text",
"selected": "Message",
"tool_mode": true,
"types": [
"Message"
],
"value": "__UNDEFINED__"
}
],
"pinned": false,
"template": {
"_type": "Component",
"code": {
"advanced": true,
"dynamic": true,
"fileTypes": [],
"file_path": "",
"info": "",
"list": false,
"load_from_db": false,
"multiline": true,
"name": "code",
"password": false,
"placeholder": "",
"required": true,
"show": true,
"title_case": false,
"type": "code",
"value": "from langflow.base.io.text import TextComponent\nfrom langflow.io import MultilineInput, Output\nfrom langflow.schema.message import Message\n\n\nclass TextInputComponent(TextComponent):\n display_name = \"Text Input\"\n description = \"Get user text inputs.\"\n documentation: str = \"https://docs.langflow.org/components-io#text-input\"\n icon = \"type\"\n name = \"TextInput\"\n\n inputs = [\n MultilineInput(\n name=\"input_value\",\n display_name=\"Text\",\n info=\"Text to be passed as input.\",\n ),\n ]\n outputs = [\n Output(display_name=\"Output Text\", name=\"text\", method=\"text_response\"),\n ]\n\n def text_response(self) -> Message:\n return Message(\n text=self.input_value,\n )\n"
},
"input_value": {
"_input_type": "MultilineInput",
"advanced": false,
"copy_field": false,
"display_name": "Text",
"dynamic": false,
"info": "Text to be passed as input.",
"input_types": [
"Message"
],
"list": false,
"list_add_label": "Add More",
"load_from_db": false,
"multiline": true,
"name": "input_value",
"placeholder": "",
"required": false,
"show": true,
"title_case": false,
"tool_mode": false,
"trace_as_input": true,
"trace_as_metadata": true,
"type": "str",
"value": "https://github.com/zjunlp/LightMem"
}
},
"tool_mode": false
},
"showNode": true,
"type": "TextInput"
},
"dragging": false,
"id": "TextInput-1pAQo",
"measured": {
"height": 204,
"width": 320
},
"position": {
"x": -1111.4767430450243,
"y": -129.2572798213864
},
"selected": false,
"type": "genericNode"
},
{
"data": {
"id": "APIRequest-inb27",
"node": {
"base_classes": [
"Data"
],
"beta": false,
"category": "data",
"conditional_paths": [],
"custom_fields": {},
"description": "Make HTTP requests using URL or cURL commands.",
"display_name": "API Request",
"documentation": "https://docs.langflow.org/components-data#api-request",
"edited": false,
"field_order": [
"url_input",
"curl_input",
"method",
"mode",
"query_params",
"body",
"headers",
"timeout",
"follow_redirects",
"save_to_file",
"include_httpx_metadata"
],
"frozen": false,
"icon": "Globe",
"key": "APIRequest",
"legacy": false,
"lf_version": "1.6.5",
"metadata": {},
"minimized": false,
"output_types": [],
"outputs": [
{
"allows_loop": false,
"cache": true,
"display_name": "API Response",
"group_outputs": false,
"method": "make_api_request",
"name": "data",
"selected": "Data",
"tool_mode": true,
"types": [
"Data"
],
"value": "__UNDEFINED__"
}
],
"pinned": false,
"score": 0.007568328950209746,
"template": {
"_type": "Component",
"body": {
"_input_type": "TableInput",
"advanced": true,
"display_name": "Body",
"dynamic": false,
"info": "The body to send with the request as a dictionary (for POST, PATCH, PUT).",
"input_types": [
"Data"
],
"is_list": true,
"list_add_label": "Add More",
"name": "body",
"placeholder": "",
"real_time_refresh": true,
"required": false,
"show": true,
"table_icon": "Table",
"table_schema": {
"columns": [
{
"default": "None",
"description": "Parameter name",
"disable_edit": false,
"display_name": "Key",
"edit_mode": "popover",
"filterable": true,
"formatter": "text",
"hidden": false,
"name": "key",
"sortable": true,
"type": "str"
},
{
"default": "None",
"description": "Parameter value",
"disable_edit": false,
"display_name": "Value",
"edit_mode": "popover",
"filterable": true,
"formatter": "text",
"hidden": false,
"name": "value",
"sortable": true
}
]
},
"title_case": false,
"tool_mode": false,
"trace_as_metadata": true,
"trigger_icon": "Table",
"trigger_text": "Open table",
"type": "table",
"value": []
},
"code": {
"advanced": true,
"dynamic": true,
"fileTypes": [],
"file_path": "",
"info": "",
"list": false,
"load_from_db": false,
"multiline": true,
"name": "code",
"password": false,
"placeholder": "",
"required": true,
"show": true,
"title_case": false,
"type": "code",
"value": "import json\nimport re\nimport tempfile\nfrom datetime import datetime, timezone\nfrom pathlib import Path\nfrom typing import Any\nfrom urllib.parse import parse_qsl, urlencode, urlparse, urlunparse\n\nimport aiofiles\nimport aiofiles.os as aiofiles_os\nimport httpx\nimport validators\n\nfrom langflow.base.curl.parse import parse_context\nfrom langflow.custom.custom_component.component import Component\nfrom langflow.inputs.inputs import TabInput\nfrom langflow.io import (\n BoolInput,\n DataInput,\n DropdownInput,\n IntInput,\n MessageTextInput,\n MultilineInput,\n Output,\n TableInput,\n)\nfrom langflow.schema.data import Data\nfrom langflow.schema.dotdict import dotdict\nfrom langflow.services.deps import get_settings_service\nfrom langflow.utils.component_utils import set_current_fields, set_field_advanced, set_field_display\n\n# Define fields for each mode\nMODE_FIELDS = {\n \"URL\": [\n \"url_input\",\n \"method\",\n ],\n \"cURL\": [\"curl_input\"],\n}\n\n# Fields that should always be visible\nDEFAULT_FIELDS = [\"mode\"]\n\n\nclass APIRequestComponent(Component):\n display_name = \"API Request\"\n description = \"Make HTTP requests using URL or cURL commands.\"\n documentation: str = \"https://docs.langflow.org/components-data#api-request\"\n icon = \"Globe\"\n name = \"APIRequest\"\n\n inputs = [\n MessageTextInput(\n name=\"url_input\",\n display_name=\"URL\",\n info=\"Enter the URL for the request.\",\n advanced=False,\n tool_mode=True,\n ),\n MultilineInput(\n name=\"curl_input\",\n display_name=\"cURL\",\n info=(\n \"Paste a curl command to populate the fields. \"\n \"This will fill in the dictionary fields for headers and body.\"\n ),\n real_time_refresh=True,\n tool_mode=True,\n advanced=True,\n show=False,\n ),\n DropdownInput(\n name=\"method\",\n display_name=\"Method\",\n options=[\"GET\", \"POST\", \"PATCH\", \"PUT\", \"DELETE\"],\n value=\"GET\",\n info=\"The HTTP method to use.\",\n real_time_refresh=True,\n ),\n TabInput(\n name=\"mode\",\n display_name=\"Mode\",\n options=[\"URL\", \"cURL\"],\n value=\"URL\",\n info=\"Enable cURL mode to populate fields from a cURL command.\",\n real_time_refresh=True,\n ),\n DataInput(\n name=\"query_params\",\n display_name=\"Query Parameters\",\n info=\"The query parameters to append to the URL.\",\n advanced=True,\n ),\n TableInput(\n name=\"body\",\n display_name=\"Body\",\n info=\"The body to send with the request as a dictionary (for POST, PATCH, PUT).\",\n table_schema=[\n {\n \"name\": \"key\",\n \"display_name\": \"Key\",\n \"type\": \"str\",\n \"description\": \"Parameter name\",\n },\n {\n \"name\": \"value\",\n \"display_name\": \"Value\",\n \"description\": \"Parameter value\",\n },\n ],\n value=[],\n input_types=[\"Data\"],\n advanced=True,\n real_time_refresh=True,\n ),\n TableInput(\n name=\"headers\",\n display_name=\"Headers\",\n info=\"The headers to send with the request\",\n table_schema=[\n {\n \"name\": \"key\",\n \"display_name\": \"Header\",\n \"type\": \"str\",\n \"description\": \"Header name\",\n },\n {\n \"name\": \"value\",\n \"display_name\": \"Value\",\n \"type\": \"str\",\n \"description\": \"Header value\",\n },\n ],\n value=[{\"key\": \"User-Agent\", \"value\": get_settings_service().settings.user_agent}],\n advanced=True,\n input_types=[\"Data\"],\n real_time_refresh=True,\n ),\n IntInput(\n name=\"timeout\",\n display_name=\"Timeout\",\n value=30,\n info=\"The timeout to use for the request.\",\n advanced=True,\n ),\n BoolInput(\n name=\"follow_redirects\",\n display_name=\"Follow Redirects\",\n value=True,\n info=\"Whether to follow http redirects.\",\n advanced=True,\n ),\n BoolInput(\n name=\"save_to_file\",\n display_name=\"Save to File\",\n value=False,\n info=\"Save the API response to a temporary file\",\n advanced=True,\n ),\n BoolInput(\n name=\"include_httpx_metadata\",\n display_name=\"Include HTTPx Metadata\",\n value=False,\n info=(\n \"Include properties such as headers, status_code, response_headers, \"\n \"and redirection_history in the output.\"\n ),\n advanced=True,\n ),\n ]\n\n outputs = [\n Output(display_name=\"API Response\", name=\"data\", method=\"make_api_request\"),\n ]\n\n def _parse_json_value(self, value: Any) -> Any:\n \"\"\"Parse a value that might be a JSON string.\"\"\"\n if not isinstance(value, str):\n return value\n\n try:\n parsed = json.loads(value)\n except json.JSONDecodeError:\n return value\n else:\n return parsed\n\n def _process_body(self, body: Any) -> dict:\n \"\"\"Process the body input into a valid dictionary.\"\"\"\n if body is None:\n return {}\n if hasattr(body, \"data\"):\n body = body.data\n if isinstance(body, dict):\n return self._process_dict_body(body)\n if isinstance(body, str):\n return self._process_string_body(body)\n if isinstance(body, list):\n return self._process_list_body(body)\n return {}\n\n def _process_dict_body(self, body: dict) -> dict:\n \"\"\"Process dictionary body by parsing JSON values.\"\"\"\n return {k: self._parse_json_value(v) for k, v in body.items()}\n\n def _process_string_body(self, body: str) -> dict:\n \"\"\"Process string body by attempting JSON parse.\"\"\"\n try:\n return self._process_body(json.loads(body))\n except json.JSONDecodeError:\n return {\"data\": body}\n\n def _process_list_body(self, body: list) -> dict:\n \"\"\"Process list body by converting to key-value dictionary.\"\"\"\n processed_dict = {}\n try:\n for item in body:\n # Unwrap Data objects\n current_item = item\n if hasattr(item, \"data\"):\n unwrapped_data = item.data\n # If the unwrapped data is a dict but not key-value format, use it directly\n if isinstance(unwrapped_data, dict) and not self._is_valid_key_value_item(unwrapped_data):\n return unwrapped_data\n current_item = unwrapped_data\n if not self._is_valid_key_value_item(current_item):\n continue\n key = current_item[\"key\"]\n value = self._parse_json_value(current_item[\"value\"])\n processed_dict[key] = value\n except (KeyError, TypeError, ValueError) as e:\n self.log(f\"Failed to process body list: {e}\")\n return {}\n return processed_dict\n\n def _is_valid_key_value_item(self, item: Any) -> bool:\n \"\"\"Check if an item is a valid key-value dictionary.\"\"\"\n return isinstance(item, dict) and \"key\" in item and \"value\" in item\n\n def parse_curl(self, curl: str, build_config: dotdict) -> dotdict:\n \"\"\"Parse a cURL command and update build configuration.\"\"\"\n try:\n parsed = parse_context(curl)\n\n # Update basic configuration\n url = parsed.url\n # Normalize URL before setting it\n url = self._normalize_url(url)\n\n build_config[\"url_input\"][\"value\"] = url\n build_config[\"method\"][\"value\"] = parsed.method.upper()\n\n # Process headers\n headers_list = [{\"key\": k, \"value\": v} for k, v in parsed.headers.items()]\n build_config[\"headers\"][\"value\"] = headers_list\n\n # Process body data\n if not parsed.data:\n build_config[\"body\"][\"value\"] = []\n elif parsed.data:\n try:\n json_data = json.loads(parsed.data)\n if isinstance(json_data, dict):\n body_list = [\n {\"key\": k, \"value\": json.dumps(v) if isinstance(v, dict | list) else str(v)}\n for k, v in json_data.items()\n ]\n build_config[\"body\"][\"value\"] = body_list\n else:\n build_config[\"body\"][\"value\"] = [{\"key\": \"data\", \"value\": json.dumps(json_data)}]\n except json.JSONDecodeError:\n build_config[\"body\"][\"value\"] = [{\"key\": \"data\", \"value\": parsed.data}]\n\n except Exception as exc:\n msg = f\"Error parsing curl: {exc}\"\n self.log(msg)\n raise ValueError(msg) from exc\n\n return build_config\n\n def _normalize_url(self, url: str) -> str:\n \"\"\"Normalize URL by adding https:// if no protocol is specified.\"\"\"\n if not url or not isinstance(url, str):\n msg = \"URL cannot be empty\"\n raise ValueError(msg)\n\n url = url.strip()\n if url.startswith((\"http://\", \"https://\")):\n return url\n return f\"https://{url}\"\n\n async def make_request(\n self,\n client: httpx.AsyncClient,\n method: str,\n url: str,\n headers: dict | None = None,\n body: Any = None,\n timeout: int = 5,\n *,\n follow_redirects: bool = True,\n save_to_file: bool = False,\n include_httpx_metadata: bool = False,\n ) -> Data:\n method = method.upper()\n if method not in {\"GET\", \"POST\", \"PATCH\", \"PUT\", \"DELETE\"}:\n msg = f\"Unsupported method: {method}\"\n raise ValueError(msg)\n\n processed_body = self._process_body(body)\n redirection_history = []\n\n try:\n # Prepare request parameters\n request_params = {\n \"method\": method,\n \"url\": url,\n \"headers\": headers,\n \"json\": processed_body,\n \"timeout\": timeout,\n \"follow_redirects\": follow_redirects,\n }\n response = await client.request(**request_params)\n\n redirection_history = [\n {\n \"url\": redirect.headers.get(\"Location\", str(redirect.url)),\n \"status_code\": redirect.status_code,\n }\n for redirect in response.history\n ]\n\n is_binary, file_path = await self._response_info(response, with_file_path=save_to_file)\n response_headers = self._headers_to_dict(response.headers)\n\n # Base metadata\n metadata = {\n \"source\": url,\n \"status_code\": response.status_code,\n \"response_headers\": response_headers,\n }\n\n if redirection_history:\n metadata[\"redirection_history\"] = redirection_history\n\n if save_to_file:\n mode = \"wb\" if is_binary else \"w\"\n encoding = response.encoding if mode == \"w\" else None\n if file_path:\n await aiofiles_os.makedirs(file_path.parent, exist_ok=True)\n if is_binary:\n async with aiofiles.open(file_path, \"wb\") as f:\n await f.write(response.content)\n await f.flush()\n else:\n async with aiofiles.open(file_path, \"w\", encoding=encoding) as f:\n await f.write(response.text)\n await f.flush()\n metadata[\"file_path\"] = str(file_path)\n\n if include_httpx_metadata:\n metadata.update({\"headers\": headers})\n return Data(data=metadata)\n\n # Handle response content\n if is_binary:\n result = response.content\n else:\n try:\n result = response.json()\n except json.JSONDecodeError:\n self.log(\"Failed to decode JSON response\")\n result = response.text.encode(\"utf-8\")\n\n metadata[\"result\"] = result\n\n if include_httpx_metadata:\n metadata.update({\"headers\": headers})\n\n return Data(data=metadata)\n except (httpx.HTTPError, httpx.RequestError, httpx.TimeoutException) as exc:\n self.log(f\"Error making request to {url}\")\n return Data(\n data={\n \"source\": url,\n \"headers\": headers,\n \"status_code\": 500,\n \"error\": str(exc),\n **({\"redirection_history\": redirection_history} if redirection_history else {}),\n },\n )\n\n def add_query_params(self, url: str, params: dict) -> str:\n \"\"\"Add query parameters to URL efficiently.\"\"\"\n if not params:\n return url\n url_parts = list(urlparse(url))\n query = dict(parse_qsl(url_parts[4]))\n query.update(params)\n url_parts[4] = urlencode(query)\n return urlunparse(url_parts)\n\n def _headers_to_dict(self, headers: httpx.Headers) -> dict[str, str]:\n \"\"\"Convert HTTP headers to a dictionary with lowercased keys.\"\"\"\n return {k.lower(): v for k, v in headers.items()}\n\n def _process_headers(self, headers: Any) -> dict:\n \"\"\"Process the headers input into a valid dictionary.\"\"\"\n if headers is None:\n return {}\n if isinstance(headers, dict):\n return headers\n if isinstance(headers, list):\n return {item[\"key\"]: item[\"value\"] for item in headers if self._is_valid_key_value_item(item)}\n return {}\n\n async def make_api_request(self) -> Data:\n \"\"\"Make HTTP request with optimized parameter handling.\"\"\"\n method = self.method\n url = self.url_input.strip() if isinstance(self.url_input, str) else \"\"\n headers = self.headers or {}\n body = self.body or {}\n timeout = self.timeout\n follow_redirects = self.follow_redirects\n save_to_file = self.save_to_file\n include_httpx_metadata = self.include_httpx_metadata\n\n # if self.mode == \"cURL\" and self.curl_input:\n # self._build_config = self.parse_curl(self.curl_input, dotdict())\n # # After parsing curl, get the normalized URL\n # url = self._build_config[\"url_input\"][\"value\"]\n\n # Normalize URL before validation\n url = self._normalize_url(url)\n\n # Validate URL\n if not validators.url(url):\n msg = f\"Invalid URL provided: {url}\"\n raise ValueError(msg)\n\n # Process query parameters\n if isinstance(self.query_params, str):\n query_params = dict(parse_qsl(self.query_params))\n else:\n query_params = self.query_params.data if self.query_params else {}\n\n # Process headers and body\n headers = self._process_headers(headers)\n body = self._process_body(body)\n url = self.add_query_params(url, query_params)\n\n async with httpx.AsyncClient() as client:\n result = await self.make_request(\n client,\n method,\n url,\n headers,\n body,\n timeout,\n follow_redirects=follow_redirects,\n save_to_file=save_to_file,\n include_httpx_metadata=include_httpx_metadata,\n )\n self.status = result\n return result\n\n def update_build_config(self, build_config: dotdict, field_value: Any, field_name: str | None = None) -> dotdict:\n \"\"\"Update the build config based on the selected mode.\"\"\"\n if field_name != \"mode\":\n if field_name == \"curl_input\" and self.mode == \"cURL\" and self.curl_input:\n return self.parse_curl(self.curl_input, build_config)\n return build_config\n\n # print(f\"Current mode: {field_value}\")\n if field_value == \"cURL\":\n set_field_display(build_config, \"curl_input\", value=True)\n if build_config[\"curl_input\"][\"value\"]:\n build_config = self.parse_curl(build_config[\"curl_input\"][\"value\"], build_config)\n else:\n set_field_display(build_config, \"curl_input\", value=False)\n\n return set_current_fields(\n build_config=build_config,\n action_fields=MODE_FIELDS,\n selected_action=field_value,\n default_fields=DEFAULT_FIELDS,\n func=set_field_advanced,\n default_value=True,\n )\n\n async def _response_info(\n self, response: httpx.Response, *, with_file_path: bool = False\n ) -> tuple[bool, Path | None]:\n \"\"\"Determine the file path and whether the response content is binary.\n\n Args:\n response (Response): The HTTP response object.\n with_file_path (bool): Whether to save the response content to a file.\n\n Returns:\n Tuple[bool, Path | None]:\n A tuple containing a boolean indicating if the content is binary and the full file path (if applicable).\n \"\"\"\n content_type = response.headers.get(\"Content-Type\", \"\")\n is_binary = \"application/octet-stream\" in content_type or \"application/binary\" in content_type\n\n if not with_file_path:\n return is_binary, None\n\n component_temp_dir = Path(tempfile.gettempdir()) / self.__class__.__name__\n\n # Create directory asynchronously\n await aiofiles_os.makedirs(component_temp_dir, exist_ok=True)\n\n filename = None\n if \"Content-Disposition\" in response.headers:\n content_disposition = response.headers[\"Content-Disposition\"]\n filename_match = re.search(r'filename=\"(.+?)\"', content_disposition)\n if filename_match:\n extracted_filename = filename_match.group(1)\n filename = extracted_filename\n\n # Step 3: Infer file extension or use part of the request URL if no filename\n if not filename:\n # Extract the last segment of the URL path\n url_path = urlparse(str(response.request.url) if response.request else \"\").path\n base_name = Path(url_path).name # Get the last segment of the path\n if not base_name: # If the path ends with a slash or is empty\n base_name = \"response\"\n\n # Infer file extension\n content_type_to_extension = {\n \"text/plain\": \".txt\",\n \"application/json\": \".json\",\n \"image/jpeg\": \".jpg\",\n \"image/png\": \".png\",\n \"application/octet-stream\": \".bin\",\n }\n extension = content_type_to_extension.get(content_type, \".bin\" if is_binary else \".txt\")\n filename = f\"{base_name}{extension}\"\n\n # Step 4: Define the full file path\n file_path = component_temp_dir / filename\n\n # Step 5: Check if file exists asynchronously and handle accordingly\n try:\n # Try to create the file exclusively (x mode) to check existence\n async with aiofiles.open(file_path, \"x\") as _:\n pass # File created successfully, we can use this path\n except FileExistsError:\n # If file exists, append a timestamp to the filename\n timestamp = datetime.now(timezone.utc).strftime(\"%Y%m%d%H%M%S%f\")\n file_path = component_temp_dir / f\"{timestamp}-{filename}\"\n\n return is_binary, file_path\n"
},
"curl_input": {
"_input_type": "MultilineInput",
"advanced": true,
"copy_field": false,
"display_name": "cURL",
"dynamic": false,
"info": "Paste a curl command to populate the fields. This will fill in the dictionary fields for headers and body.",
"input_types": [
"Message"
],
"list": false,
"list_add_label": "Add More",
"load_from_db": false,
"multiline": true,
"name": "curl_input",
"placeholder": "",
"real_time_refresh": true,
"required": false,
"show": false,
"title_case": false,
"tool_mode": true,
"trace_as_input": true,
"trace_as_metadata": true,
"type": "str",
"value": ""
},
"follow_redirects": {
"_input_type": "BoolInput",
"advanced": true,
"display_name": "Follow Redirects",
"dynamic": false,
"info": "Whether to follow http redirects.",
"list": false,
"list_add_label": "Add More",
"name": "follow_redirects",
"placeholder": "",
"required": false,
"show": true,
"title_case": false,
"tool_mode": false,
"trace_as_metadata": true,
"type": "bool",
"value": true
},
"headers": {
"_input_type": "TableInput",
"advanced": true,
"display_name": "Headers",
"dynamic": false,
"info": "The headers to send with the request",
"input_types": [
"Data"
],
"is_list": true,
"list_add_label": "Add More",
"name": "headers",
"placeholder": "",
"real_time_refresh": true,
"required": false,
"show": true,
"table_icon": "Table",
"table_schema": {
"columns": [
{
"default": "None",
"description": "Header name",
"disable_edit": false,
"display_name": "Header",
"edit_mode": "popover",
"filterable": true,
"formatter": "text",
"hidden": false,
"name": "key",
"sortable": true,
"type": "str"
},
{
"default": "None",
"description": "Header value",
"disable_edit": false,
"display_name": "Value",
"edit_mode": "popover",
"filterable": true,
"formatter": "text",
"hidden": false,
"name": "value",
"sortable": true,
"type": "str"
}
]
},
"title_case": false,
"tool_mode": false,
"trace_as_metadata": true,
"trigger_icon": "Table",
"trigger_text": "Open table",
"type": "table",
"value": [
{
"key": "User-Agent",
"value": "langflow"
}
]
},
"include_httpx_metadata": {
"_input_type": "BoolInput",
"advanced": true,
"display_name": "Include HTTPx Metadata",
"dynamic": false,
"info": "Include properties such as headers, status_code, response_headers, and redirection_history in the output.",
"list": false,
"list_add_label": "Add More",
"name": "include_httpx_metadata",
"placeholder": "",
"required": false,
"show": true,
"title_case": false,
"tool_mode": false,
"trace_as_metadata": true,
"type": "bool",
"value": false
},
"method": {
"_input_type": "DropdownInput",
"advanced": false,
"combobox": false,
"dialog_inputs": {},
"display_name": "Method",
"dynamic": false,
"external_options": {},
"info": "The HTTP method to use.",
"name": "method",
"options": [
"GET",
"POST",
"PATCH",
"PUT",
"DELETE"
],
"options_metadata": [],
"placeholder": "",
"real_time_refresh": true,
"required": false,
"show": true,
"title_case": false,
"toggle": false,
"tool_mode": false,
"trace_as_metadata": true,
"type": "str",
"value": "GET"
},
"mode": {
"_input_type": "TabInput",
"advanced": false,
"display_name": "Mode",
"dynamic": false,
"info": "Enable cURL mode to populate fields from a cURL command.",
"name": "mode",
"options": [
"URL",
"cURL"
],
"placeholder": "",
"real_time_refresh": true,
"required": false,
"show": true,
"title_case": false,
"tool_mode": false,
"trace_as_metadata": true,
"type": "tab",
"value": "URL"
},
"query_params": {
"_input_type": "DataInput",
"advanced": true,
"display_name": "Query Parameters",
"dynamic": false,
"info": "The query parameters to append to the URL.",
"input_types": [
"Data"
],
"list": false,
"list_add_label": "Add More",
"name": "query_params",
"placeholder": "",
"required": false,
"show": true,
"title_case": false,
"tool_mode": false,
"trace_as_input": true,
"trace_as_metadata": true,
"type": "other",
"value": ""
},
"save_to_file": {
"_input_type": "BoolInput",
"advanced": true,
"display_name": "Save to File",
"dynamic": false,
"info": "Save the API response to a temporary file",
"list": false,
"list_add_label": "Add More",
"name": "save_to_file",
"placeholder": "",
"required": false,
"show": true,
"title_case": false,
"tool_mode": false,
"trace_as_metadata": true,
"type": "bool",
"value": false
},
"timeout": {
"_input_type": "IntInput",
"advanced": true,
"display_name": "Timeout",
"dynamic": false,
"info": "The timeout to use for the request.",
"list": false,
"list_add_label": "Add More",
"name": "timeout",
"placeholder": "",
"required": false,
"show": true,
"title_case": false,
"tool_mode": false,
"trace_as_metadata": true,
"type": "int",
"value": 30
},
"url_input": {
"_input_type": "MessageTextInput",
"advanced": false,
"display_name": "URL",
"dynamic": false,
"info": "Enter the URL for the request.",
"input_types": [
"Message"
],
"list": false,
"list_add_label": "Add More",
"load_from_db": false,
"name": "url_input",
"placeholder": "",
"required": false,
"show": true,
"title_case": false,
"tool_mode": true,
"trace_as_input": true,
"trace_as_metadata": true,
"type": "str",
"value": ""
}
},
"tool_mode": false
},
"showNode": true,
"type": "APIRequest"
},
"dragging": false,
"id": "APIRequest-inb27",
"measured": {
"height": 383,
"width": 320
},
"position": {
"x": -649.2135579959204,
"y": 14.533197548495025
},
"selected": false,
"type": "genericNode"
},
{
"data": {
"id": "ParserComponent-O4WyQ",
"node": {
"base_classes": [
"Message"
],
"beta": false,
"category": "processing",
"conditional_paths": [],
"custom_fields": {},
"description": "Extracts text using a template.",
"display_name": "Parser",
"documentation": "https://docs.langflow.org/components-processing#parser",
"edited": false,
"field_order": [
"input_data",
"mode",
"pattern",
"sep"
],
"frozen": false,
"icon": "braces",
"key": "ParserComponent",
"last_updated": "2025-11-09T23:14:36.735Z",
"legacy": false,
"lf_version": "1.6.5",
"metadata": {},
"minimized": false,
"output_types": [],
"outputs": [
{
"allows_loop": false,
"cache": true,
"display_name": "Parsed Text",
"group_outputs": false,
"method": "parse_combined_text",
"name": "parsed_text",
"options": null,
"required_inputs": null,
"selected": "Message",
"tool_mode": true,
"types": [
"Message"
],
"value": "__UNDEFINED__"
}
],
"pinned": false,
"score": 0.001,
"template": {
"_type": "Component",
"clean_data": {
"_input_type": "BoolInput",
"advanced": true,
"display_name": "Clean Data",
"dynamic": false,
"info": "Enable to clean the data by removing empty rows and lines in each cell of the DataFrame/ Data object.",
"list": false,
"list_add_label": "Add More",
"name": "clean_data",
"placeholder": "",
"required": false,
"show": true,
"title_case": false,
"tool_mode": false,
"trace_as_metadata": true,
"type": "bool",
"value": true
},
"code": {
"advanced": true,
"dynamic": true,
"fileTypes": [],
"file_path": "",
"info": "",
"list": false,
"load_from_db": false,
"multiline": true,
"name": "code",
"password": false,
"placeholder": "",
"required": true,
"show": true,
"title_case": false,
"type": "code",
"value": "from langflow.custom.custom_component.component import Component\nfrom langflow.helpers.data import safe_convert\nfrom langflow.inputs.inputs import BoolInput, HandleInput, MessageTextInput, MultilineInput, TabInput\nfrom langflow.schema.data import Data\nfrom langflow.schema.dataframe import DataFrame\nfrom langflow.schema.message import Message\nfrom langflow.template.field.base import Output\n\n\nclass ParserComponent(Component):\n display_name = \"Parser\"\n description = \"Extracts text using a template.\"\n documentation: str = \"https://docs.langflow.org/components-processing#parser\"\n icon = \"braces\"\n\n inputs = [\n HandleInput(\n name=\"input_data\",\n display_name=\"Data or DataFrame\",\n input_types=[\"DataFrame\", \"Data\"],\n info=\"Accepts either a DataFrame or a Data object.\",\n required=True,\n ),\n TabInput(\n name=\"mode\",\n display_name=\"Mode\",\n options=[\"Parser\", \"Stringify\"],\n value=\"Parser\",\n info=\"Convert into raw string instead of using a template.\",\n real_time_refresh=True,\n ),\n MultilineInput(\n name=\"pattern\",\n display_name=\"Template\",\n info=(\n \"Use variables within curly brackets to extract column values for DataFrames \"\n \"or key values for Data.\"\n \"For example: `Name: {Name}, Age: {Age}, Country: {Country}`\"\n ),\n value=\"Text: {text}\", # Example default\n dynamic=True,\n show=True,\n required=True,\n ),\n MessageTextInput(\n name=\"sep\",\n display_name=\"Separator\",\n advanced=True,\n value=\"\\n\",\n info=\"String used to separate rows/items.\",\n ),\n ]\n\n outputs = [\n Output(\n display_name=\"Parsed Text\",\n name=\"parsed_text\",\n info=\"Formatted text output.\",\n method=\"parse_combined_text\",\n ),\n ]\n\n def update_build_config(self, build_config, field_value, field_name=None):\n \"\"\"Dynamically hide/show `template` and enforce requirement based on `stringify`.\"\"\"\n if field_name == \"mode\":\n build_config[\"pattern\"][\"show\"] = self.mode == \"Parser\"\n build_config[\"pattern\"][\"required\"] = self.mode == \"Parser\"\n if field_value:\n clean_data = BoolInput(\n name=\"clean_data\",\n display_name=\"Clean Data\",\n info=(\n \"Enable to clean the data by removing empty rows and lines \"\n \"in each cell of the DataFrame/ Data object.\"\n ),\n value=True,\n advanced=True,\n required=False,\n )\n build_config[\"clean_data\"] = clean_data.to_dict()\n else:\n build_config.pop(\"clean_data\", None)\n\n return build_config\n\n def _clean_args(self):\n \"\"\"Prepare arguments based on input type.\"\"\"\n input_data = self.input_data\n\n match input_data:\n case list() if all(isinstance(item, Data) for item in input_data):\n msg = \"List of Data objects is not supported.\"\n raise ValueError(msg)\n case DataFrame():\n return input_data, None\n case Data():\n return None, input_data\n case dict() if \"data\" in input_data:\n try:\n if \"columns\" in input_data: # Likely a DataFrame\n return DataFrame.from_dict(input_data), None\n # Likely a Data object\n return None, Data(**input_data)\n except (TypeError, ValueError, KeyError) as e:\n msg = f\"Invalid structured input provided: {e!s}\"\n raise ValueError(msg) from e\n case _:\n msg = f\"Unsupported input type: {type(input_data)}. Expected DataFrame or Data.\"\n raise ValueError(msg)\n\n def parse_combined_text(self) -> Message:\n \"\"\"Parse all rows/items into a single text or convert input to string if `stringify` is enabled.\"\"\"\n # Early return for stringify option\n if self.mode == \"Stringify\":\n return self.convert_to_string()\n\n df, data = self._clean_args()\n\n lines = []\n if df is not None:\n for _, row in df.iterrows():\n formatted_text = self.pattern.format(**row.to_dict())\n lines.append(formatted_text)\n elif data is not None:\n formatted_text = self.pattern.format(**data.data)\n lines.append(formatted_text)\n\n combined_text = self.sep.join(lines)\n self.status = combined_text\n return Message(text=combined_text)\n\n def convert_to_string(self) -> Message:\n \"\"\"Convert input data to string with proper error handling.\"\"\"\n result = \"\"\n if isinstance(self.input_data, list):\n result = \"\\n\".join([safe_convert(item, clean_data=self.clean_data or False) for item in self.input_data])\n else:\n result = safe_convert(self.input_data or False)\n self.log(f\"Converted to string with length: {len(result)}\")\n\n message = Message(text=result)\n self.status = message\n return message\n"
},
"input_data": {
"_input_type": "HandleInput",
"advanced": false,
"display_name": "Data or DataFrame",
"dynamic": false,
"info": "Accepts either a DataFrame or a Data object.",
"input_types": [
"DataFrame",
"Data"
],
"list": false,
"list_add_label": "Add More",
"name": "input_data",
"placeholder": "",
"required": true,
"show": true,
"title_case": false,
"trace_as_metadata": true,
"type": "other",
"value": ""
},
"mode": {
"_input_type": "TabInput",
"advanced": false,
"display_name": "Mode",
"dynamic": false,
"info": "Convert into raw string instead of using a template.",
"name": "mode",
"options": [
"Parser",
"Stringify"
],
"placeholder": "",
"real_time_refresh": true,
"required": false,
"show": true,
"title_case": false,
"tool_mode": false,
"trace_as_metadata": true,
"type": "tab",
"value": "Stringify"
},
"pattern": {
"_input_type": "MultilineInput",
"advanced": false,
"copy_field": false,
"display_name": "Template",
"dynamic": true,
"info": "Use variables within curly brackets to extract column values for DataFrames or key values for Data.For example: `Name: {Name}, Age: {Age}, Country: {Country}`",
"input_types": [
"Message"
],
"list": false,
"list_add_label": "Add More",
"load_from_db": false,
"multiline": true,
"name": "pattern",
"placeholder": "",
"required": false,
"show": false,
"title_case": false,
"tool_mode": false,
"trace_as_input": true,
"trace_as_metadata": true,
"type": "str",
"value": "{text}"
},
"sep": {
"_input_type": "MessageTextInput",
"advanced": true,
"display_name": "Separator",
"dynamic": false,
"info": "String used to separate rows/items.",
"input_types": [
"Message"
],
"list": false,
"list_add_label": "Add More",
"load_from_db": false,
"name": "sep",
"placeholder": "",
"required": false,
"show": true,
"title_case": false,
"tool_mode": false,
"trace_as_input": true,
"trace_as_metadata": true,
"type": "str",
"value": "\n"
}
},
"tool_mode": false
},
"showNode": true,
"type": "ParserComponent"
},
"dragging": false,
"id": "ParserComponent-O4WyQ",
"measured": {
"height": 246,
"width": 320
},
"position": {
"x": -234.8915775816668,
"y": 188.0921642269375
},
"selected": false,
"type": "genericNode"
},
{
"data": {
"id": "LanguageModelComponent-cxlCl",
"node": {
"base_classes": [
"LanguageModel",
"Message"
],
"beta": false,
"conditional_paths": [],
"custom_fields": {},
"description": "Runs a language model given a specified provider.",
"display_name": "Language Model",
"documentation": "https://docs.langflow.org/components-models",
"edited": false,
"field_order": [
"provider",
"model_name",
"api_key",
"input_value",
"system_message",
"stream",
"temperature"
],
"frozen": false,
"icon": "brain-circuit",
"last_updated": "2025-11-13T05:47:22.533Z",
"legacy": false,
"lf_version": "1.6.5",
"metadata": {
"keywords": [
"model",
"llm",
"language model",
"large language model"
]
},
"minimized": false,
"output_types": [],
"outputs": [
{
"allows_loop": false,
"cache": true,
"display_name": "Model Response",
"group_outputs": false,
"method": "text_response",
"name": "text_output",
"options": null,
"required_inputs": null,
"selected": "Message",
"tool_mode": true,
"types": [
"Message"
],
"value": "__UNDEFINED__"
},
{
"allows_loop": false,
"cache": true,
"display_name": "Language Model",
"group_outputs": false,
"method": "build_model",
"name": "model_output",
"options": null,
"required_inputs": null,
"selected": "LanguageModel",
"tool_mode": true,
"types": [
"LanguageModel"
],
"value": "__UNDEFINED__"
}
],
"pinned": false,
"priority": 0,
"template": {
"_type": "Component",
"api_key": {
"_input_type": "SecretStrInput",
"advanced": false,
"display_name": "OpenAI API Key",
"dynamic": false,
"info": "Model Provider API key",
"input_types": [],
"load_from_db": true,
"name": "api_key",
"password": true,
"placeholder": "",
"real_time_refresh": true,
"required": false,
"show": true,
"title_case": false,
"type": "str",
"value": ""
},
"code": {
"advanced": true,
"dynamic": true,
"fileTypes": [],
"file_path": "",
"info": "",
"list": false,
"load_from_db": false,
"multiline": true,
"name": "code",
"password": false,
"placeholder": "",
"required": true,
"show": true,
"title_case": false,
"type": "code",
"value": "from typing import Any\n\nfrom langchain_anthropic import ChatAnthropic\nfrom langchain_google_genai import ChatGoogleGenerativeAI\nfrom langchain_openai import ChatOpenAI\n\nfrom langflow.base.models.anthropic_constants import ANTHROPIC_MODELS\nfrom langflow.base.models.google_generative_ai_constants import GOOGLE_GENERATIVE_AI_MODELS\nfrom langflow.base.models.model import LCModelComponent\nfrom langflow.base.models.openai_constants import OPENAI_CHAT_MODEL_NAMES, OPENAI_REASONING_MODEL_NAMES\nfrom langflow.field_typing import LanguageModel\nfrom langflow.field_typing.range_spec import RangeSpec\nfrom langflow.inputs.inputs import BoolInput\nfrom langflow.io import DropdownInput, MessageInput, MultilineInput, SecretStrInput, SliderInput\nfrom langflow.schema.dotdict import dotdict\n\n\nclass LanguageModelComponent(LCModelComponent):\n display_name = \"Language Model\"\n description = \"Runs a language model given a specified provider.\"\n documentation: str = \"https://docs.langflow.org/components-models\"\n icon = \"brain-circuit\"\n category = \"models\"\n priority = 0 # Set priority to 0 to make it appear first\n\n inputs = [\n DropdownInput(\n name=\"provider\",\n display_name=\"Model Provider\",\n options=[\"OpenAI\", \"Anthropic\", \"Google\"],\n value=\"OpenAI\",\n info=\"Select the model provider\",\n real_time_refresh=True,\n options_metadata=[{\"icon\": \"OpenAI\"}, {\"icon\": \"Anthropic\"}, {\"icon\": \"GoogleGenerativeAI\"}],\n ),\n DropdownInput(\n name=\"model_name\",\n display_name=\"Model Name\",\n options=OPENAI_CHAT_MODEL_NAMES + OPENAI_REASONING_MODEL_NAMES,\n value=OPENAI_CHAT_MODEL_NAMES[0],\n info=\"Select the model to use\",\n real_time_refresh=True,\n ),\n SecretStrInput(\n name=\"api_key\",\n display_name=\"OpenAI API Key\",\n info=\"Model Provider API key\",\n required=False,\n show=True,\n real_time_refresh=True,\n ),\n MessageInput(\n name=\"input_value\",\n display_name=\"Input\",\n info=\"The input text to send to the model\",\n ),\n MultilineInput(\n name=\"system_message\",\n display_name=\"System Message\",\n info=\"A system message that helps set the behavior of the assistant\",\n advanced=False,\n ),\n BoolInput(\n name=\"stream\",\n display_name=\"Stream\",\n info=\"Whether to stream the response\",\n value=False,\n advanced=True,\n ),\n SliderInput(\n name=\"temperature\",\n display_name=\"Temperature\",\n value=0.1,\n info=\"Controls randomness in responses\",\n range_spec=RangeSpec(min=0, max=1, step=0.01),\n advanced=True,\n ),\n ]\n\n def build_model(self) -> LanguageModel:\n provider = self.provider\n model_name = self.model_name\n temperature = self.temperature\n stream = self.stream\n\n if provider == \"OpenAI\":\n if not self.api_key:\n msg = \"OpenAI API key is required when using OpenAI provider\"\n raise ValueError(msg)\n\n if model_name in OPENAI_REASONING_MODEL_NAMES:\n # reasoning models do not support temperature (yet)\n temperature = None\n\n return ChatOpenAI(\n model_name=model_name,\n temperature=temperature,\n streaming=stream,\n openai_api_key=self.api_key,\n )\n if provider == \"Anthropic\":\n if not self.api_key:\n msg = \"Anthropic API key is required when using Anthropic provider\"\n raise ValueError(msg)\n return ChatAnthropic(\n model=model_name,\n temperature=temperature,\n streaming=stream,\n anthropic_api_key=self.api_key,\n )\n if provider == \"Google\":\n if not self.api_key:\n msg = \"Google API key is required when using Google provider\"\n raise ValueError(msg)\n return ChatGoogleGenerativeAI(\n model=model_name,\n temperature=temperature,\n streaming=stream,\n google_api_key=self.api_key,\n )\n msg = f\"Unknown provider: {provider}\"\n raise ValueError(msg)\n\n def update_build_config(self, build_config: dotdict, field_value: Any, field_name: str | None = None) -> dotdict:\n if field_name == \"provider\":\n if field_value == \"OpenAI\":\n build_config[\"model_name\"][\"options\"] = OPENAI_CHAT_MODEL_NAMES + OPENAI_REASONING_MODEL_NAMES\n build_config[\"model_name\"][\"value\"] = OPENAI_CHAT_MODEL_NAMES[0]\n build_config[\"api_key\"][\"display_name\"] = \"OpenAI API Key\"\n elif field_value == \"Anthropic\":\n build_config[\"model_name\"][\"options\"] = ANTHROPIC_MODELS\n build_config[\"model_name\"][\"value\"] = ANTHROPIC_MODELS[0]\n build_config[\"api_key\"][\"display_name\"] = \"Anthropic API Key\"\n elif field_value == \"Google\":\n build_config[\"model_name\"][\"options\"] = GOOGLE_GENERATIVE_AI_MODELS\n build_config[\"model_name\"][\"value\"] = GOOGLE_GENERATIVE_AI_MODELS[0]\n build_config[\"api_key\"][\"display_name\"] = \"Google API Key\"\n elif field_name == \"model_name\" and field_value.startswith(\"o1\") and self.provider == \"OpenAI\":\n # Hide system_message for o1 models - currently unsupported\n if \"system_message\" in build_config:\n build_config[\"system_message\"][\"show\"] = False\n elif field_name == \"model_name\" and not field_value.startswith(\"o1\") and \"system_message\" in build_config:\n build_config[\"system_message\"][\"show\"] = True\n return build_config\n"
},
"input_value": {
"_input_type": "MessageInput",
"advanced": false,
"display_name": "Input",
"dynamic": false,
"info": "The input text to send to the model",
"input_types": [
"Message"
],
"list": false,
"list_add_label": "Add More",
"load_from_db": false,
"name": "input_value",
"placeholder": "",
"required": false,
"show": true,
"title_case": false,
"tool_mode": false,
"trace_as_input": true,
"trace_as_metadata": true,
"type": "str",
"value": ""
},
"model_name": {
"_input_type": "DropdownInput",
"advanced": false,
"combobox": false,
"dialog_inputs": {},
"display_name": "Model Name",
"dynamic": false,
"external_options": {},
"info": "Select the model to use",
"name": "model_name",
"options": [
"gpt-4o-mini",
"gpt-4o",
"gpt-4.1",
"gpt-4.1-mini",
"gpt-4.1-nano",
"gpt-4-turbo",
"gpt-4-turbo-preview",
"gpt-4",
"gpt-3.5-turbo",
"gpt-5",
"gpt-5-mini",
"gpt-5-nano",
"gpt-5-chat-latest",
"o1",
"o3-mini",
"o3",
"o3-pro",
"o4-mini",
"o4-mini-high"
],
"options_metadata": [],
"placeholder": "",
"real_time_refresh": true,
"required": false,
"show": true,
"title_case": false,
"toggle": false,
"tool_mode": false,
"trace_as_metadata": true,
"type": "str",
"value": "gpt-5-nano"
},
"provider": {
"_input_type": "DropdownInput",
"advanced": false,
"combobox": false,
"dialog_inputs": {},
"display_name": "Model Provider",
"dynamic": false,
"external_options": {},
"info": "Select the model provider",
"name": "provider",
"options": [
"OpenAI",
"Anthropic",
"Google"
],
"options_metadata": [
{
"icon": "OpenAI"
},
{
"icon": "Anthropic"
},
{
"icon": "GoogleGenerativeAI"
}
],
"placeholder": "",
"real_time_refresh": true,
"required": false,
"show": true,
"title_case": false,
"toggle": false,
"tool_mode": false,
"trace_as_metadata": true,
"type": "str",
"value": "OpenAI"
},
"stream": {
"_input_type": "BoolInput",
"advanced": true,
"display_name": "Stream",
"dynamic": false,
"info": "Whether to stream the response",
"list": false,
"list_add_label": "Add More",
"name": "stream",
"placeholder": "",
"required": false,
"show": true,
"title_case": false,
"tool_mode": false,
"trace_as_metadata": true,
"type": "bool",
"value": false
},
"system_message": {
"_input_type": "MultilineInput",
"advanced": false,
"copy_field": false,
"display_name": "System Message",
"dynamic": false,
"info": "A system message that helps set the behavior of the assistant",
"input_types": [
"Message"
],
"list": false,
"list_add_label": "Add More",
"load_from_db": false,
"multiline": true,
"name": "system_message",
"placeholder": "",
"required": false,
"show": true,
"title_case": false,
"tool_mode": false,
"trace_as_input": true,
"trace_as_metadata": true,
"type": "str",
"value": "You are the editor of a developer email newsletter. You are interested in developer related content and you need to share short summaries of the content you are given.\n\nYou should return, as Markdown, the title of the piece of content you are given, then a short summary of the content. No more than 3 sentences. You should also tag the content as one of the following:\n\n\"Building with AI, Agents & MCP\": this is for tutorials or other content that deals with building AI agents or working with MCP servers.\n\"Other news\": this is for content that is about AI or agents, but isn't a tutorial or other information on how to build.\n\"Code & Libraries\": this is for code or libraries specifically, normally this is a GitHub repo or HuggingFace space, but could be links to code stored elsewhere\n\nFor example, your output might look like:\n\n## Agents Rule of Two: A Practical Approach to AI Agent Security\n\nMeta released this article on the Agents Rule of Two. By only allowing agents unsupervised use of tools from two out of the three categories of the lethal trifecta (access to private data, exposure to untrusted content, and a method of external communication), you can avoid this category of problems.\n\ntags: Building with AI, Agents & MCP"
},
"temperature": {
"_input_type": "SliderInput",
"advanced": true,
"display_name": "Temperature",
"dynamic": false,
"info": "Controls randomness in responses",
"max_label": "",
"max_label_icon": "",
"min_label": "",
"min_label_icon": "",
"name": "temperature",
"placeholder": "",
"range_spec": {
"max": 1,
"min": 0,
"step": 0.01,
"step_type": "float"
},
"required": false,
"show": true,
"slider_buttons": false,
"slider_buttons_options": [],
"slider_input": false,
"title_case": false,
"tool_mode": false,
"type": "slider",
"value": 0.1
}
},
"tool_mode": false
},
"selected_output": "text_output",
"showNode": true,
"type": "LanguageModelComponent"
},
"dragging": false,
"id": "LanguageModelComponent-cxlCl",
"measured": {
"height": 534,
"width": 320
},
"position": {
"x": 190.903999881703,
"y": -19.960633037314608
},
"selected": false,
"type": "genericNode"
},
{
"data": {
"id": "Prompt Template-HJD20",
"node": {
"base_classes": [
"Message"
],
"beta": false,
"conditional_paths": [],
"custom_fields": {
"template": [
"content",
"url"
]
},
"description": "Create a prompt template with dynamic variables.",
"display_name": "Prompt Template",
"documentation": "https://docs.langflow.org/components-prompts",
"edited": false,
"error": null,
"field_order": [
"template",
"tool_placeholder"
],
"frozen": false,
"full_path": null,
"icon": "braces",
"is_composition": null,
"is_input": null,
"is_output": null,
"legacy": false,
"lf_version": "1.6.5",
"metadata": {},
"minimized": false,
"name": "",
"output_types": [],
"outputs": [
{
"allows_loop": false,
"cache": true,
"display_name": "Prompt",
"group_outputs": false,
"hidden": null,
"method": "build_prompt",
"name": "prompt",
"options": null,
"required_inputs": null,
"selected": "Message",
"tool_mode": true,
"types": [
"Message"
],
"value": "__UNDEFINED__"
}
],
"pinned": false,
"priority": 0,
"replacement": null,
"template": {
"_type": "Component",
"code": {
"advanced": true,
"dynamic": true,
"fileTypes": [],
"file_path": "",
"info": "",
"list": false,
"load_from_db": false,
"multiline": true,
"name": "code",
"password": false,
"placeholder": "",
"required": true,
"show": true,
"title_case": false,
"type": "code",
"value": "from langflow.base.prompts.api_utils import process_prompt_template\nfrom langflow.custom.custom_component.component import Component\nfrom langflow.inputs.inputs import DefaultPromptField\nfrom langflow.io import MessageTextInput, Output, PromptInput\nfrom langflow.schema.message import Message\nfrom langflow.template.utils import update_template_values\n\n\nclass PromptComponent(Component):\n display_name: str = \"Prompt Template\"\n description: str = \"Create a prompt template with dynamic variables.\"\n documentation: str = \"https://docs.langflow.org/components-prompts\"\n icon = \"braces\"\n trace_type = \"prompt\"\n name = \"Prompt Template\"\n priority = 0 # Set priority to 0 to make it appear first\n\n inputs = [\n PromptInput(name=\"template\", display_name=\"Template\"),\n MessageTextInput(\n name=\"tool_placeholder\",\n display_name=\"Tool Placeholder\",\n tool_mode=True,\n advanced=True,\n info=\"A placeholder input for tool mode.\",\n ),\n ]\n\n outputs = [\n Output(display_name=\"Prompt\", name=\"prompt\", method=\"build_prompt\"),\n ]\n\n async def build_prompt(self) -> Message:\n prompt = Message.from_template(**self._attributes)\n self.status = prompt.text\n return prompt\n\n def _update_template(self, frontend_node: dict):\n prompt_template = frontend_node[\"template\"][\"template\"][\"value\"]\n custom_fields = frontend_node[\"custom_fields\"]\n frontend_node_template = frontend_node[\"template\"]\n _ = process_prompt_template(\n template=prompt_template,\n name=\"template\",\n custom_fields=custom_fields,\n frontend_node_template=frontend_node_template,\n )\n return frontend_node\n\n async def update_frontend_node(self, new_frontend_node: dict, current_frontend_node: dict):\n \"\"\"This function is called after the code validation is done.\"\"\"\n frontend_node = await super().update_frontend_node(new_frontend_node, current_frontend_node)\n template = frontend_node[\"template\"][\"template\"][\"value\"]\n # Kept it duplicated for backwards compatibility\n _ = process_prompt_template(\n template=template,\n name=\"template\",\n custom_fields=frontend_node[\"custom_fields\"],\n frontend_node_template=frontend_node[\"template\"],\n )\n # Now that template is updated, we need to grab any values that were set in the current_frontend_node\n # and update the frontend_node with those values\n update_template_values(new_template=frontend_node, previous_template=current_frontend_node[\"template\"])\n return frontend_node\n\n def _get_fallback_input(self, **kwargs):\n return DefaultPromptField(**kwargs)\n"
},
"content": {
"advanced": false,
"display_name": "content",
"dynamic": false,
"field_type": "str",
"fileTypes": [],
"file_path": "",
"info": "",
"input_types": [
"Message"
],
"list": false,
"load_from_db": false,
"multiline": true,
"name": "content",
"placeholder": "",
"required": false,
"show": true,
"title_case": false,
"type": "str",
"value": ""
},
"template": {
"_input_type": "PromptInput",
"advanced": false,
"display_name": "Template",
"dynamic": false,
"info": "",
"list": false,
"list_add_label": "Add More",
"name": "template",
"placeholder": "",
"required": false,
"show": true,
"title_case": false,
"tool_mode": false,
"trace_as_input": true,
"type": "prompt",
"value": "{content}\n\n[{url}]({url})"
},
"tool_placeholder": {
"_input_type": "MessageTextInput",
"advanced": true,
"display_name": "Tool Placeholder",
"dynamic": false,
"info": "A placeholder input for tool mode.",
"input_types": [
"Message"
],
"list": false,
"list_add_label": "Add More",
"load_from_db": false,
"name": "tool_placeholder",
"placeholder": "",
"required": false,
"show": true,
"title_case": false,
"tool_mode": true,
"trace_as_input": true,
"trace_as_metadata": true,
"type": "str",
"value": ""
},
"url": {
"advanced": false,
"display_name": "url",
"dynamic": false,
"field_type": "str",
"fileTypes": [],
"file_path": "",
"info": "",
"input_types": [
"Message"
],
"list": false,
"load_from_db": false,
"multiline": true,
"name": "url",
"placeholder": "",
"required": false,
"show": true,
"title_case": false,
"type": "str",
"value": ""
}
},
"tool_mode": false
},
"showNode": true,
"type": "Prompt Template"
},
"dragging": false,
"id": "Prompt Template-HJD20",
"measured": {
"height": 403,
"width": 320
},
"position": {
"x": 648.1100532929737,
"y": 62.83096064411328
},
"selected": false,
"type": "genericNode"
},
{
"data": {
"id": "note-MhHwY",
"node": {
"description": "# Text Input\nThis should receive a URL for an article via the API.",
"display_name": "",
"documentation": "",
"template": {}
},
"type": "note"
},
"dragging": false,
"height": 348,
"id": "note-MhHwY",
"measured": {
"height": 348,
"width": 364
},
"position": {
"x": -1132.382630066189,
"y": -253.94746601530827
},
"resizing": false,
"selected": false,
"type": "noteNode",
"width": 364
},
{
"data": {
"id": "note-sBFbJ",
"node": {
"description": "# API Request\n\nThis module fetches the content from the URL, but it can do a lot more, making any type of HTTP request, adding headers and a body.",
"display_name": "",
"documentation": "",
"template": {
"backgroundColor": "rose"
}
},
"type": "note"
},
"dragging": false,
"height": 572,
"id": "note-sBFbJ",
"measured": {
"height": 572,
"width": 398
},
"position": {
"x": -687.0054086428759,
"y": -151.39873298561517
},
"resizing": false,
"selected": false,
"type": "noteNode",
"width": 398
},
{
"data": {
"id": "note-zIdW4",
"node": {
"description": "# Parser\n\nThis takes the data returned from the API Request and stringifies it so that the model can understand it.",
"display_name": "",
"documentation": "",
"template": {
"backgroundColor": "rose"
}
},
"type": "note"
},
"dragging": false,
"height": 437,
"id": "note-zIdW4",
"measured": {
"height": 437,
"width": 357
},
"position": {
"x": -254.71120368846522,
"y": 20.346057742396788
},
"resizing": false,
"selected": false,
"type": "noteNode",
"width": 357
},
{
"data": {
"id": "note-UtIgY",
"node": {
"description": "# Language model\n\nThis is where the AI magic happens. Combining the system message that directs the model what to do and the contents from the URL results in an article title and summary that will be useful later.\n",
"display_name": "",
"documentation": "",
"template": {
"backgroundColor": "lime"
}
},
"type": "note"
},
"dragging": false,
"height": 770,
"id": "note-UtIgY",
"measured": {
"height": 770,
"width": 399
},
"position": {
"x": 152.77335727523263,
"y": -218.752010377565
},
"resizing": false,
"selected": false,
"type": "noteNode",
"width": 399
},
{
"data": {
"id": "note-BtY8o",
"node": {
"description": "# Prompt template\n\nCombines the model response with the original URL so we don't lose the link when we save it in Notion.",
"display_name": "",
"documentation": "",
"template": {
"backgroundColor": "rose"
}
},
"type": "note"
},
"dragging": false,
"height": 598,
"id": "note-BtY8o",
"measured": {
"height": 598,
"width": 373
},
"position": {
"x": 621.4415834889401,
"y": -97.89935410372928
},
"resizing": false,
"selected": false,
"type": "noteNode",
"width": 373
},
{
"data": {
"id": "note-bqyKX",
"node": {
"description": "# Add Content to Notion Page\n\nThis component appends the content from the template to the Notion page. ",
"display_name": "",
"documentation": "",
"template": {
"backgroundColor": "blue"
}
},
"type": "note"
},
"dragging": false,
"height": 637,
"id": "note-bqyKX",
"measured": {
"height": 637,
"width": 389
},
"position": {
"x": 1077.6952852803727,
"y": -186.19358611532382
},
"resizing": false,
"selected": false,
"type": "noteNode",
"width": 389
}
],
"viewport": {
"x": -494.2034833150349,
"y": 536.3883220620014,
"zoom": 1.0687604799788486
}
},
"description": "",
"endpoint_name": null,
"id": "9847ed68-3d0a-4260-989c-79e9efcb867b",
"is_component": false,
"last_tested_version": "1.6.5",
"name": "Newsletter helper",
"tags": []
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment