feat: add notion integration components (#3579)
This commit is contained in:
parent
63cf0cff29
commit
03e95d4522
17 changed files with 1098 additions and 4 deletions
13
poetry.lock
generated
13
poetry.lock
generated
|
|
@ -10259,6 +10259,17 @@ files = [
|
|||
{file = "types_google_cloud_ndb-2.3.0.20240813-py3-none-any.whl", hash = "sha256:79404e04e97324d0b6466f297e92e734a38fb9cd064c2f3816820311bc6c3f57"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "types-markdown"
|
||||
version = "3.7.0.20240822"
|
||||
description = "Typing stubs for Markdown"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "types-Markdown-3.7.0.20240822.tar.gz", hash = "sha256:183557c9f4f865bdefd8f5f96a38145c31819271cde111d35557c3bd2069e78d"},
|
||||
{file = "types_Markdown-3.7.0.20240822-py3-none-any.whl", hash = "sha256:bec91c410aaf2470ffdb103e38438fbcc53689b00133f19e64869eb138432ad7"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "types-passlib"
|
||||
version = "1.7.7.20240327"
|
||||
|
|
@ -11560,4 +11571,4 @@ local = ["ctransformers", "llama-cpp-python", "sentence-transformers"]
|
|||
[metadata]
|
||||
lock-version = "2.0"
|
||||
python-versions = ">=3.10,<3.13"
|
||||
content-hash = "3bdfc3e3b86f7e417c34972e5e2251d079602df87650bdc6d6b56d846dbc8a48"
|
||||
content-hash = "51dc3a97f0153a6e8a6810bfea823b200a9fc6565b2103b8cafc29c937629e0a"
|
||||
|
|
|
|||
|
|
@ -136,6 +136,7 @@ vulture = "^2.11"
|
|||
dictdiffer = "^0.9.0"
|
||||
pytest-split = "^0.9.0"
|
||||
pytest-flakefinder = "^1.1.0"
|
||||
types-markdown = "^3.7.0.20240822"
|
||||
|
||||
[tool.poetry.extras]
|
||||
deploy = ["celery", "redis", "flower"]
|
||||
|
|
|
|||
0
src/backend/base/langflow/components/Notion/__init__.py
Normal file
0
src/backend/base/langflow/components/Notion/__init__.py
Normal file
|
|
@ -0,0 +1,268 @@
|
|||
import json
|
||||
from typing import Dict, Any, Union
|
||||
from markdown import markdown
|
||||
from bs4 import BeautifulSoup
|
||||
import requests
|
||||
|
||||
from langflow.base.langchain_utilities.model import LCToolComponent
|
||||
from langflow.inputs import SecretStrInput, StrInput, MultilineInput
|
||||
from langflow.schema import Data
|
||||
from langflow.field_typing import Tool
|
||||
from langchain.tools import StructuredTool
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
class AddContentToPage(LCToolComponent):
|
||||
display_name: str = "Add Content to Page "
|
||||
description: str = "Convert markdown text to Notion blocks and append them to a Notion page."
|
||||
documentation: str = "https://developers.notion.com/reference/patch-block-children"
|
||||
icon = "NotionDirectoryLoader"
|
||||
|
||||
inputs = [
|
||||
MultilineInput(
|
||||
name="markdown_text",
|
||||
display_name="Markdown Text",
|
||||
info="The markdown text to convert to Notion blocks.",
|
||||
),
|
||||
StrInput(
|
||||
name="block_id",
|
||||
display_name="Page/Block ID",
|
||||
info="The ID of the page/block to add the content.",
|
||||
),
|
||||
SecretStrInput(
|
||||
name="notion_secret",
|
||||
display_name="Notion Secret",
|
||||
info="The Notion integration token.",
|
||||
required=True,
|
||||
),
|
||||
]
|
||||
|
||||
class AddContentToPageSchema(BaseModel):
|
||||
markdown_text: str = Field(..., description="The markdown text to convert to Notion blocks.")
|
||||
block_id: str = Field(..., description="The ID of the page/block to add the content.")
|
||||
|
||||
def run_model(self) -> Data:
|
||||
result = self._add_content_to_page(self.markdown_text, self.block_id)
|
||||
return Data(data=result, text=json.dumps(result))
|
||||
|
||||
def build_tool(self) -> Tool:
|
||||
return StructuredTool.from_function(
|
||||
name="add_content_to_notion_page",
|
||||
description="Convert markdown text to Notion blocks and append them to a Notion page.",
|
||||
func=self._add_content_to_page,
|
||||
args_schema=self.AddContentToPageSchema,
|
||||
)
|
||||
|
||||
def _add_content_to_page(self, markdown_text: str, block_id: str) -> Union[Dict[str, Any], str]:
|
||||
try:
|
||||
html_text = markdown(markdown_text)
|
||||
soup = BeautifulSoup(html_text, "html.parser")
|
||||
blocks = self.process_node(soup)
|
||||
|
||||
url = f"https://api.notion.com/v1/blocks/{block_id}/children"
|
||||
headers = {
|
||||
"Authorization": f"Bearer {self.notion_secret}",
|
||||
"Content-Type": "application/json",
|
||||
"Notion-Version": "2022-06-28",
|
||||
}
|
||||
|
||||
data = {
|
||||
"children": blocks,
|
||||
}
|
||||
|
||||
response = requests.patch(url, headers=headers, json=data)
|
||||
response.raise_for_status()
|
||||
|
||||
return response.json()
|
||||
except requests.exceptions.RequestException as e:
|
||||
error_message = f"Error: Failed to add content to Notion page. {str(e)}"
|
||||
if hasattr(e, "response") and e.response is not None:
|
||||
error_message += f" Status code: {e.response.status_code}, Response: {e.response.text}"
|
||||
return error_message
|
||||
except Exception as e:
|
||||
return f"Error: An unexpected error occurred while adding content to Notion page. {str(e)}"
|
||||
|
||||
def process_node(self, node):
|
||||
blocks = []
|
||||
if isinstance(node, str):
|
||||
text = node.strip()
|
||||
if text:
|
||||
if text.startswith("#"):
|
||||
heading_level = text.count("#", 0, 6)
|
||||
heading_text = text[heading_level:].strip()
|
||||
if heading_level == 1:
|
||||
blocks.append(self.create_block("heading_1", heading_text))
|
||||
elif heading_level == 2:
|
||||
blocks.append(self.create_block("heading_2", heading_text))
|
||||
elif heading_level == 3:
|
||||
blocks.append(self.create_block("heading_3", heading_text))
|
||||
else:
|
||||
blocks.append(self.create_block("paragraph", text))
|
||||
elif node.name == "h1":
|
||||
blocks.append(self.create_block("heading_1", node.get_text(strip=True)))
|
||||
elif node.name == "h2":
|
||||
blocks.append(self.create_block("heading_2", node.get_text(strip=True)))
|
||||
elif node.name == "h3":
|
||||
blocks.append(self.create_block("heading_3", node.get_text(strip=True)))
|
||||
elif node.name == "p":
|
||||
code_node = node.find("code")
|
||||
if code_node:
|
||||
code_text = code_node.get_text()
|
||||
language, code = self.extract_language_and_code(code_text)
|
||||
blocks.append(self.create_block("code", code, language=language))
|
||||
elif self.is_table(str(node)):
|
||||
blocks.extend(self.process_table(node))
|
||||
else:
|
||||
blocks.append(self.create_block("paragraph", node.get_text(strip=True)))
|
||||
elif node.name == "ul":
|
||||
blocks.extend(self.process_list(node, "bulleted_list_item"))
|
||||
elif node.name == "ol":
|
||||
blocks.extend(self.process_list(node, "numbered_list_item"))
|
||||
elif node.name == "blockquote":
|
||||
blocks.append(self.create_block("quote", node.get_text(strip=True)))
|
||||
elif node.name == "hr":
|
||||
blocks.append(self.create_block("divider", ""))
|
||||
elif node.name == "img":
|
||||
blocks.append(self.create_block("image", "", image_url=node.get("src")))
|
||||
elif node.name == "a":
|
||||
blocks.append(self.create_block("bookmark", node.get_text(strip=True), link_url=node.get("href")))
|
||||
elif node.name == "table":
|
||||
blocks.extend(self.process_table(node))
|
||||
|
||||
for child in node.children:
|
||||
if isinstance(child, str):
|
||||
continue
|
||||
blocks.extend(self.process_node(child))
|
||||
|
||||
return blocks
|
||||
|
||||
def extract_language_and_code(self, code_text):
|
||||
lines = code_text.split("\n")
|
||||
language = lines[0].strip()
|
||||
code = "\n".join(lines[1:]).strip()
|
||||
return language, code
|
||||
|
||||
def is_code_block(self, text):
|
||||
return text.startswith("```")
|
||||
|
||||
def extract_code_block(self, text):
|
||||
lines = text.split("\n")
|
||||
language = lines[0].strip("`").strip()
|
||||
code = "\n".join(lines[1:]).strip("`").strip()
|
||||
return language, code
|
||||
|
||||
def is_table(self, text):
|
||||
rows = text.split("\n")
|
||||
if len(rows) < 2:
|
||||
return False
|
||||
|
||||
has_separator = False
|
||||
for i, row in enumerate(rows):
|
||||
if "|" in row:
|
||||
cells = [cell.strip() for cell in row.split("|")]
|
||||
cells = [cell for cell in cells if cell] # Remove empty cells
|
||||
if i == 1 and all(set(cell) <= set("-|") for cell in cells):
|
||||
has_separator = True
|
||||
elif not cells:
|
||||
return False
|
||||
|
||||
return has_separator and len(rows) >= 3
|
||||
|
||||
def process_list(self, node, list_type):
|
||||
blocks = []
|
||||
for item in node.find_all("li"):
|
||||
item_text = item.get_text(strip=True)
|
||||
checked = item_text.startswith("[x]")
|
||||
is_checklist = item_text.startswith("[ ]") or checked
|
||||
|
||||
if is_checklist:
|
||||
item_text = item_text.replace("[x]", "").replace("[ ]", "").strip()
|
||||
blocks.append(self.create_block("to_do", item_text, checked=checked))
|
||||
else:
|
||||
blocks.append(self.create_block(list_type, item_text))
|
||||
return blocks
|
||||
|
||||
def process_table(self, node):
|
||||
blocks = []
|
||||
header_row = node.find("thead").find("tr") if node.find("thead") else None
|
||||
body_rows = node.find("tbody").find_all("tr") if node.find("tbody") else []
|
||||
|
||||
if header_row or body_rows:
|
||||
table_width = max(
|
||||
len(header_row.find_all(["th", "td"])) if header_row else 0,
|
||||
max(len(row.find_all(["th", "td"])) for row in body_rows),
|
||||
)
|
||||
|
||||
table_block = self.create_block("table", "", table_width=table_width, has_column_header=bool(header_row))
|
||||
blocks.append(table_block)
|
||||
|
||||
if header_row:
|
||||
header_cells = [cell.get_text(strip=True) for cell in header_row.find_all(["th", "td"])]
|
||||
header_row_block = self.create_block("table_row", header_cells)
|
||||
blocks.append(header_row_block)
|
||||
|
||||
for row in body_rows:
|
||||
cells = [cell.get_text(strip=True) for cell in row.find_all(["th", "td"])]
|
||||
row_block = self.create_block("table_row", cells)
|
||||
blocks.append(row_block)
|
||||
|
||||
return blocks
|
||||
|
||||
def create_block(self, block_type: str, content: str, **kwargs) -> Dict[str, Any]:
|
||||
block: dict[str, Any] = {
|
||||
"object": "block",
|
||||
"type": block_type,
|
||||
block_type: {},
|
||||
}
|
||||
|
||||
if block_type in [
|
||||
"paragraph",
|
||||
"heading_1",
|
||||
"heading_2",
|
||||
"heading_3",
|
||||
"bulleted_list_item",
|
||||
"numbered_list_item",
|
||||
"quote",
|
||||
]:
|
||||
block[block_type]["rich_text"] = [
|
||||
{
|
||||
"type": "text",
|
||||
"text": {
|
||||
"content": content,
|
||||
},
|
||||
}
|
||||
]
|
||||
elif block_type == "to_do":
|
||||
block[block_type]["rich_text"] = [
|
||||
{
|
||||
"type": "text",
|
||||
"text": {
|
||||
"content": content,
|
||||
},
|
||||
}
|
||||
]
|
||||
block[block_type]["checked"] = kwargs.get("checked", False)
|
||||
elif block_type == "code":
|
||||
block[block_type]["rich_text"] = [
|
||||
{
|
||||
"type": "text",
|
||||
"text": {
|
||||
"content": content,
|
||||
},
|
||||
}
|
||||
]
|
||||
block[block_type]["language"] = kwargs.get("language", "plain text")
|
||||
elif block_type == "image":
|
||||
block[block_type] = {"type": "external", "external": {"url": kwargs.get("image_url", "")}}
|
||||
elif block_type == "divider":
|
||||
pass
|
||||
elif block_type == "bookmark":
|
||||
block[block_type]["url"] = kwargs.get("link_url", "")
|
||||
elif block_type == "table":
|
||||
block[block_type]["table_width"] = kwargs.get("table_width", 0)
|
||||
block[block_type]["has_column_header"] = kwargs.get("has_column_header", False)
|
||||
block[block_type]["has_row_header"] = kwargs.get("has_row_header", False)
|
||||
elif block_type == "table_row":
|
||||
block[block_type]["cells"] = [[{"type": "text", "text": {"content": cell}} for cell in content]]
|
||||
|
||||
return block
|
||||
93
src/backend/base/langflow/components/Notion/create_page.py
Normal file
93
src/backend/base/langflow/components/Notion/create_page.py
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
import json
|
||||
from typing import Dict, Any, Union
|
||||
import requests
|
||||
from pydantic import BaseModel, Field
|
||||
from langflow.base.langchain_utilities.model import LCToolComponent
|
||||
from langflow.inputs import SecretStrInput, StrInput, MultilineInput
|
||||
from langflow.schema import Data
|
||||
from langflow.field_typing import Tool
|
||||
from langchain.tools import StructuredTool
|
||||
|
||||
|
||||
class NotionPageCreator(LCToolComponent):
|
||||
display_name: str = "Create Page "
|
||||
description: str = "A component for creating Notion pages."
|
||||
documentation: str = "https://docs.langflow.org/integrations/notion/page-create"
|
||||
icon = "NotionDirectoryLoader"
|
||||
|
||||
inputs = [
|
||||
StrInput(
|
||||
name="database_id",
|
||||
display_name="Database ID",
|
||||
info="The ID of the Notion database.",
|
||||
),
|
||||
SecretStrInput(
|
||||
name="notion_secret",
|
||||
display_name="Notion Secret",
|
||||
info="The Notion integration token.",
|
||||
required=True,
|
||||
),
|
||||
MultilineInput(
|
||||
name="properties_json",
|
||||
display_name="Properties (JSON)",
|
||||
info="The properties of the new page as a JSON string.",
|
||||
),
|
||||
]
|
||||
|
||||
class NotionPageCreatorSchema(BaseModel):
|
||||
database_id: str = Field(..., description="The ID of the Notion database.")
|
||||
properties_json: str = Field(..., description="The properties of the new page as a JSON string.")
|
||||
|
||||
def run_model(self) -> Data:
|
||||
result = self._create_notion_page(self.database_id, self.properties_json)
|
||||
if isinstance(result, str):
|
||||
# An error occurred, return it as text
|
||||
return Data(text=result)
|
||||
else:
|
||||
# Success, return the created page data
|
||||
output = "Created page properties:\n"
|
||||
for prop_name, prop_value in result.get("properties", {}).items():
|
||||
output += f"{prop_name}: {prop_value}\n"
|
||||
return Data(text=output, data=result)
|
||||
|
||||
def build_tool(self) -> Tool:
|
||||
return StructuredTool.from_function(
|
||||
name="create_notion_page",
|
||||
description="Create a new page in a Notion database. IMPORTANT: Use the tool to check the Database properties for more details before using this tool.",
|
||||
func=self._create_notion_page,
|
||||
args_schema=self.NotionPageCreatorSchema,
|
||||
)
|
||||
|
||||
def _create_notion_page(self, database_id: str, properties_json: str) -> Union[Dict[str, Any], str]:
|
||||
if not database_id or not properties_json:
|
||||
return "Invalid input. Please provide 'database_id' and 'properties_json'."
|
||||
|
||||
try:
|
||||
properties = json.loads(properties_json)
|
||||
except json.JSONDecodeError as e:
|
||||
return f"Invalid properties format. Please provide a valid JSON string. Error: {str(e)}"
|
||||
|
||||
headers = {
|
||||
"Authorization": f"Bearer {self.notion_secret}",
|
||||
"Content-Type": "application/json",
|
||||
"Notion-Version": "2022-06-28",
|
||||
}
|
||||
|
||||
data = {
|
||||
"parent": {"database_id": database_id},
|
||||
"properties": properties,
|
||||
}
|
||||
|
||||
try:
|
||||
response = requests.post("https://api.notion.com/v1/pages", headers=headers, json=data)
|
||||
response.raise_for_status()
|
||||
result = response.json()
|
||||
return result
|
||||
except requests.exceptions.RequestException as e:
|
||||
error_message = f"Failed to create Notion page. Error: {str(e)}"
|
||||
if hasattr(e, "response") and e.response is not None:
|
||||
error_message += f" Status code: {e.response.status_code}, Response: {e.response.text}"
|
||||
return error_message
|
||||
|
||||
def __call__(self, *args, **kwargs):
|
||||
return self._create_notion_page(*args, **kwargs)
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
import requests
|
||||
from typing import Dict, Union
|
||||
from pydantic import BaseModel, Field
|
||||
from langflow.base.langchain_utilities.model import LCToolComponent
|
||||
from langflow.inputs import SecretStrInput, StrInput
|
||||
from langflow.schema import Data
|
||||
from langflow.field_typing import Tool
|
||||
from langchain.tools import StructuredTool
|
||||
|
||||
|
||||
class NotionDatabaseProperties(LCToolComponent):
|
||||
display_name: str = "List Database Properties "
|
||||
description: str = "Retrieve properties of a Notion database."
|
||||
documentation: str = "https://docs.langflow.org/integrations/notion/list-database-properties"
|
||||
icon = "NotionDirectoryLoader"
|
||||
|
||||
inputs = [
|
||||
StrInput(
|
||||
name="database_id",
|
||||
display_name="Database ID",
|
||||
info="The ID of the Notion database.",
|
||||
),
|
||||
SecretStrInput(
|
||||
name="notion_secret",
|
||||
display_name="Notion Secret",
|
||||
info="The Notion integration token.",
|
||||
required=True,
|
||||
),
|
||||
]
|
||||
|
||||
class NotionDatabasePropertiesSchema(BaseModel):
|
||||
database_id: str = Field(..., description="The ID of the Notion database.")
|
||||
|
||||
def run_model(self) -> Data:
|
||||
result = self._fetch_database_properties(self.database_id)
|
||||
if isinstance(result, str):
|
||||
# An error occurred, return it as text
|
||||
return Data(text=result)
|
||||
else:
|
||||
# Success, return the properties
|
||||
return Data(text=str(result), data=result)
|
||||
|
||||
def build_tool(self) -> Tool:
|
||||
return StructuredTool.from_function(
|
||||
name="notion_database_properties",
|
||||
description="Retrieve properties of a Notion database. Input should include the database ID.",
|
||||
func=self._fetch_database_properties,
|
||||
args_schema=self.NotionDatabasePropertiesSchema,
|
||||
)
|
||||
|
||||
def _fetch_database_properties(self, database_id: str) -> Union[Dict, str]:
|
||||
url = f"https://api.notion.com/v1/databases/{database_id}"
|
||||
headers = {
|
||||
"Authorization": f"Bearer {self.notion_secret}",
|
||||
"Notion-Version": "2022-06-28", # Use the latest supported version
|
||||
}
|
||||
try:
|
||||
response = requests.get(url, headers=headers)
|
||||
response.raise_for_status()
|
||||
data = response.json()
|
||||
properties = data.get("properties", {})
|
||||
return properties
|
||||
except requests.exceptions.RequestException as e:
|
||||
return f"Error fetching Notion database properties: {str(e)}"
|
||||
except ValueError as e:
|
||||
return f"Error parsing Notion API response: {str(e)}"
|
||||
except Exception as e:
|
||||
return f"An unexpected error occurred: {str(e)}"
|
||||
116
src/backend/base/langflow/components/Notion/list_pages.py
Normal file
116
src/backend/base/langflow/components/Notion/list_pages.py
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
import requests
|
||||
import json
|
||||
from typing import Dict, Any, List, Optional
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from langflow.base.langchain_utilities.model import LCToolComponent
|
||||
from langflow.inputs import SecretStrInput, StrInput, MultilineInput
|
||||
from langflow.schema import Data
|
||||
from langflow.field_typing import Tool
|
||||
from langchain.tools import StructuredTool
|
||||
|
||||
|
||||
class NotionListPages(LCToolComponent):
|
||||
display_name: str = "List Pages "
|
||||
description: str = (
|
||||
"Query a Notion database with filtering and sorting. "
|
||||
"The input should be a JSON string containing the 'filter' and 'sorts' objects. "
|
||||
"Example input:\n"
|
||||
'{"filter": {"property": "Status", "select": {"equals": "Done"}}, "sorts": [{"timestamp": "created_time", "direction": "descending"}]}'
|
||||
)
|
||||
documentation: str = "https://docs.langflow.org/integrations/notion/list-pages"
|
||||
icon = "NotionDirectoryLoader"
|
||||
|
||||
inputs = [
|
||||
SecretStrInput(
|
||||
name="notion_secret",
|
||||
display_name="Notion Secret",
|
||||
info="The Notion integration token.",
|
||||
required=True,
|
||||
),
|
||||
StrInput(
|
||||
name="database_id",
|
||||
display_name="Database ID",
|
||||
info="The ID of the Notion database to query.",
|
||||
),
|
||||
MultilineInput(
|
||||
name="query_json",
|
||||
display_name="Database query (JSON)",
|
||||
info="A JSON string containing the filters and sorts that will be used for querying the database. Leave empty for no filters or sorts.",
|
||||
),
|
||||
]
|
||||
|
||||
class NotionListPagesSchema(BaseModel):
|
||||
database_id: str = Field(..., description="The ID of the Notion database to query.")
|
||||
query_json: Optional[str] = Field(
|
||||
default="",
|
||||
description="A JSON string containing the filters and sorts for querying the database. Leave empty for no filters or sorts.",
|
||||
)
|
||||
|
||||
def run_model(self) -> List[Data]:
|
||||
result = self._query_notion_database(self.database_id, self.query_json)
|
||||
|
||||
if isinstance(result, str):
|
||||
# An error occurred, return it as a single record
|
||||
return [Data(text=result)]
|
||||
|
||||
records = []
|
||||
combined_text = f"Pages found: {len(result)}\n\n"
|
||||
|
||||
for page in result:
|
||||
page_data = {
|
||||
"id": page["id"],
|
||||
"url": page["url"],
|
||||
"created_time": page["created_time"],
|
||||
"last_edited_time": page["last_edited_time"],
|
||||
"properties": page["properties"],
|
||||
}
|
||||
|
||||
text = (
|
||||
f"id: {page['id']}\n"
|
||||
f"url: {page['url']}\n"
|
||||
f"created_time: {page['created_time']}\n"
|
||||
f"last_edited_time: {page['last_edited_time']}\n"
|
||||
f"properties: {json.dumps(page['properties'], indent=2)}\n\n"
|
||||
)
|
||||
|
||||
combined_text += text
|
||||
records.append(Data(text=text, **page_data))
|
||||
|
||||
self.status = records
|
||||
return records
|
||||
|
||||
def build_tool(self) -> Tool:
|
||||
return StructuredTool.from_function(
|
||||
name="notion_list_pages",
|
||||
description=self.description,
|
||||
func=self._query_notion_database,
|
||||
args_schema=self.NotionListPagesSchema,
|
||||
)
|
||||
|
||||
def _query_notion_database(self, database_id: str, query_json: Optional[str] = None) -> List[Dict[str, Any]] | str:
|
||||
url = f"https://api.notion.com/v1/databases/{database_id}/query"
|
||||
headers = {
|
||||
"Authorization": f"Bearer {self.notion_secret}",
|
||||
"Content-Type": "application/json",
|
||||
"Notion-Version": "2022-06-28",
|
||||
}
|
||||
|
||||
query_payload = {}
|
||||
if query_json and query_json.strip():
|
||||
try:
|
||||
query_payload = json.loads(query_json)
|
||||
except json.JSONDecodeError as e:
|
||||
return f"Invalid JSON format for query: {str(e)}"
|
||||
|
||||
try:
|
||||
response = requests.post(url, headers=headers, json=query_payload)
|
||||
response.raise_for_status()
|
||||
results = response.json()
|
||||
return results["results"]
|
||||
except requests.exceptions.RequestException as e:
|
||||
return f"Error querying Notion database: {str(e)}"
|
||||
except KeyError:
|
||||
return "Unexpected response format from Notion API"
|
||||
except Exception as e:
|
||||
return f"An unexpected error occurred: {str(e)}"
|
||||
78
src/backend/base/langflow/components/Notion/list_users.py
Normal file
78
src/backend/base/langflow/components/Notion/list_users.py
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
import requests
|
||||
from typing import List, Dict
|
||||
from pydantic import BaseModel
|
||||
|
||||
from langflow.base.langchain_utilities.model import LCToolComponent
|
||||
from langflow.inputs import SecretStrInput
|
||||
from langflow.schema import Data
|
||||
from langflow.field_typing import Tool
|
||||
from langchain.tools import StructuredTool
|
||||
|
||||
|
||||
class NotionUserList(LCToolComponent):
|
||||
display_name = "List Users "
|
||||
description = "Retrieve users from Notion."
|
||||
documentation = "https://docs.langflow.org/integrations/notion/list-users"
|
||||
icon = "NotionDirectoryLoader"
|
||||
|
||||
inputs = [
|
||||
SecretStrInput(
|
||||
name="notion_secret",
|
||||
display_name="Notion Secret",
|
||||
info="The Notion integration token.",
|
||||
required=True,
|
||||
),
|
||||
]
|
||||
|
||||
class NotionUserListSchema(BaseModel):
|
||||
pass
|
||||
|
||||
def run_model(self) -> List[Data]:
|
||||
users = self._list_users()
|
||||
records = []
|
||||
combined_text = ""
|
||||
|
||||
for user in users:
|
||||
output = "User:\n"
|
||||
for key, value in user.items():
|
||||
output += f"{key.replace('_', ' ').title()}: {value}\n"
|
||||
output += "________________________\n"
|
||||
|
||||
combined_text += output
|
||||
records.append(Data(text=output, data=user))
|
||||
|
||||
self.status = records
|
||||
return records
|
||||
|
||||
def build_tool(self) -> Tool:
|
||||
return StructuredTool.from_function(
|
||||
name="notion_list_users",
|
||||
description="Retrieve users from Notion.",
|
||||
func=self._list_users,
|
||||
args_schema=self.NotionUserListSchema,
|
||||
)
|
||||
|
||||
def _list_users(self) -> List[Dict]:
|
||||
url = "https://api.notion.com/v1/users"
|
||||
headers = {
|
||||
"Authorization": f"Bearer {self.notion_secret}",
|
||||
"Notion-Version": "2022-06-28",
|
||||
}
|
||||
|
||||
response = requests.get(url, headers=headers)
|
||||
response.raise_for_status()
|
||||
|
||||
data = response.json()
|
||||
results = data["results"]
|
||||
|
||||
users = []
|
||||
for user in results:
|
||||
user_data = {
|
||||
"id": user["id"],
|
||||
"type": user["type"],
|
||||
"name": user.get("name", ""),
|
||||
"avatar_url": user.get("avatar_url", ""),
|
||||
}
|
||||
users.append(user_data)
|
||||
|
||||
return users
|
||||
|
|
@ -0,0 +1,91 @@
|
|||
import requests
|
||||
from pydantic import BaseModel, Field
|
||||
from langflow.base.langchain_utilities.model import LCToolComponent
|
||||
from langflow.inputs import SecretStrInput, StrInput
|
||||
from langflow.schema import Data
|
||||
from langflow.field_typing import Tool
|
||||
from langchain.tools import StructuredTool
|
||||
|
||||
|
||||
class NotionPageContent(LCToolComponent):
|
||||
display_name = "Page Content Viewer "
|
||||
description = "Retrieve the content of a Notion page as plain text."
|
||||
documentation = "https://docs.langflow.org/integrations/notion/page-content-viewer"
|
||||
icon = "NotionDirectoryLoader"
|
||||
|
||||
inputs = [
|
||||
StrInput(
|
||||
name="page_id",
|
||||
display_name="Page ID",
|
||||
info="The ID of the Notion page to retrieve.",
|
||||
),
|
||||
SecretStrInput(
|
||||
name="notion_secret",
|
||||
display_name="Notion Secret",
|
||||
info="The Notion integration token.",
|
||||
required=True,
|
||||
),
|
||||
]
|
||||
|
||||
class NotionPageContentSchema(BaseModel):
|
||||
page_id: str = Field(..., description="The ID of the Notion page to retrieve.")
|
||||
|
||||
def run_model(self) -> Data:
|
||||
result = self._retrieve_page_content(self.page_id)
|
||||
if isinstance(result, str) and result.startswith("Error:"):
|
||||
# An error occurred, return it as text
|
||||
return Data(text=result)
|
||||
else:
|
||||
# Success, return the content
|
||||
return Data(text=result, data={"content": result})
|
||||
|
||||
def build_tool(self) -> Tool:
|
||||
return StructuredTool.from_function(
|
||||
name="notion_page_content",
|
||||
description="Retrieve the content of a Notion page as plain text.",
|
||||
func=self._retrieve_page_content,
|
||||
args_schema=self.NotionPageContentSchema,
|
||||
)
|
||||
|
||||
def _retrieve_page_content(self, page_id: str) -> str:
|
||||
blocks_url = f"https://api.notion.com/v1/blocks/{page_id}/children?page_size=100"
|
||||
headers = {
|
||||
"Authorization": f"Bearer {self.notion_secret}",
|
||||
"Notion-Version": "2022-06-28",
|
||||
}
|
||||
try:
|
||||
blocks_response = requests.get(blocks_url, headers=headers)
|
||||
blocks_response.raise_for_status()
|
||||
blocks_data = blocks_response.json()
|
||||
return self.parse_blocks(blocks_data.get("results", []))
|
||||
except requests.exceptions.RequestException as e:
|
||||
error_message = f"Error: Failed to retrieve Notion page content. {str(e)}"
|
||||
if hasattr(e, "response") and e.response is not None:
|
||||
error_message += f" Status code: {e.response.status_code}, Response: {e.response.text}"
|
||||
return error_message
|
||||
except Exception as e:
|
||||
return f"Error: An unexpected error occurred while retrieving Notion page content. {str(e)}"
|
||||
|
||||
def parse_blocks(self, blocks: list) -> str:
|
||||
content = ""
|
||||
for block in blocks:
|
||||
block_type = block.get("type")
|
||||
if block_type in ["paragraph", "heading_1", "heading_2", "heading_3", "quote"]:
|
||||
content += self.parse_rich_text(block[block_type].get("rich_text", [])) + "\n\n"
|
||||
elif block_type in ["bulleted_list_item", "numbered_list_item"]:
|
||||
content += self.parse_rich_text(block[block_type].get("rich_text", [])) + "\n"
|
||||
elif block_type == "to_do":
|
||||
content += self.parse_rich_text(block["to_do"].get("rich_text", [])) + "\n"
|
||||
elif block_type == "code":
|
||||
content += self.parse_rich_text(block["code"].get("rich_text", [])) + "\n\n"
|
||||
elif block_type == "image":
|
||||
content += f"[Image: {block['image'].get('external', {}).get('url', 'No URL')}]\n\n"
|
||||
elif block_type == "divider":
|
||||
content += "---\n\n"
|
||||
return content.strip()
|
||||
|
||||
def parse_rich_text(self, rich_text: list) -> str:
|
||||
return "".join(segment.get("plain_text", "") for segment in rich_text)
|
||||
|
||||
def __call__(self, *args, **kwargs):
|
||||
return self._retrieve_page_content(*args, **kwargs)
|
||||
109
src/backend/base/langflow/components/Notion/search.py
Normal file
109
src/backend/base/langflow/components/Notion/search.py
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
import requests
|
||||
from typing import Dict, Any, List
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from langflow.base.langchain_utilities.model import LCToolComponent
|
||||
from langflow.inputs import SecretStrInput, StrInput, DropdownInput
|
||||
from langflow.schema import Data
|
||||
from langflow.field_typing import Tool
|
||||
from langchain.tools import StructuredTool
|
||||
|
||||
|
||||
class NotionSearch(LCToolComponent):
|
||||
display_name: str = "Search "
|
||||
description: str = "Searches all pages and databases that have been shared with an integration."
|
||||
documentation: str = "https://docs.langflow.org/integrations/notion/search"
|
||||
icon = "NotionDirectoryLoader"
|
||||
|
||||
inputs = [
|
||||
SecretStrInput(
|
||||
name="notion_secret",
|
||||
display_name="Notion Secret",
|
||||
info="The Notion integration token.",
|
||||
required=True,
|
||||
),
|
||||
StrInput(
|
||||
name="query",
|
||||
display_name="Search Query",
|
||||
info="The text that the API compares page and database titles against.",
|
||||
),
|
||||
DropdownInput(
|
||||
name="filter_value",
|
||||
display_name="Filter Type",
|
||||
info="Limits the results to either only pages or only databases.",
|
||||
options=["page", "database"],
|
||||
value="page",
|
||||
),
|
||||
DropdownInput(
|
||||
name="sort_direction",
|
||||
display_name="Sort Direction",
|
||||
info="The direction to sort the results.",
|
||||
options=["ascending", "descending"],
|
||||
value="descending",
|
||||
),
|
||||
]
|
||||
|
||||
class NotionSearchSchema(BaseModel):
|
||||
query: str = Field(..., description="The search query text.")
|
||||
filter_value: str = Field(default="page", description="Filter type: 'page' or 'database'.")
|
||||
sort_direction: str = Field(default="descending", description="Sort direction: 'ascending' or 'descending'.")
|
||||
|
||||
def run_model(self) -> List[Data]:
|
||||
results = self._search_notion(self.query, self.filter_value, self.sort_direction)
|
||||
records = []
|
||||
combined_text = f"Results found: {len(results)}\n\n"
|
||||
|
||||
for result in results:
|
||||
result_data = {
|
||||
"id": result["id"],
|
||||
"type": result["object"],
|
||||
"last_edited_time": result["last_edited_time"],
|
||||
}
|
||||
|
||||
if result["object"] == "page":
|
||||
result_data["title_or_url"] = result["url"]
|
||||
text = f"id: {result['id']}\ntitle_or_url: {result['url']}\n"
|
||||
elif result["object"] == "database":
|
||||
if "title" in result and isinstance(result["title"], list) and len(result["title"]) > 0:
|
||||
result_data["title_or_url"] = result["title"][0]["plain_text"]
|
||||
text = f"id: {result['id']}\ntitle_or_url: {result['title'][0]['plain_text']}\n"
|
||||
else:
|
||||
result_data["title_or_url"] = "N/A"
|
||||
text = f"id: {result['id']}\ntitle_or_url: N/A\n"
|
||||
|
||||
text += f"type: {result['object']}\nlast_edited_time: {result['last_edited_time']}\n\n"
|
||||
combined_text += text
|
||||
records.append(Data(text=text, data=result_data))
|
||||
|
||||
self.status = records
|
||||
return records
|
||||
|
||||
def build_tool(self) -> Tool:
|
||||
return StructuredTool.from_function(
|
||||
name="notion_search",
|
||||
description="Search Notion pages and databases. Input should include the search query and optionally filter type and sort direction.",
|
||||
func=self._search_notion,
|
||||
args_schema=self.NotionSearchSchema,
|
||||
)
|
||||
|
||||
def _search_notion(
|
||||
self, query: str, filter_value: str = "page", sort_direction: str = "descending"
|
||||
) -> List[Dict[str, Any]]:
|
||||
url = "https://api.notion.com/v1/search"
|
||||
headers = {
|
||||
"Authorization": f"Bearer {self.notion_secret}",
|
||||
"Content-Type": "application/json",
|
||||
"Notion-Version": "2022-06-28",
|
||||
}
|
||||
|
||||
data = {
|
||||
"query": query,
|
||||
"filter": {"value": filter_value, "property": "object"},
|
||||
"sort": {"direction": sort_direction, "timestamp": "last_edited_time"},
|
||||
}
|
||||
|
||||
response = requests.post(url, headers=headers, json=data)
|
||||
response.raise_for_status()
|
||||
|
||||
results = response.json()
|
||||
return results["results"]
|
||||
|
|
@ -0,0 +1,111 @@
|
|||
import json
|
||||
import requests
|
||||
from typing import Dict, Any, Union
|
||||
from pydantic import BaseModel, Field
|
||||
from langflow.base.langchain_utilities.model import LCToolComponent
|
||||
from langflow.inputs import SecretStrInput, StrInput, MultilineInput
|
||||
from langflow.schema import Data
|
||||
from langflow.field_typing import Tool
|
||||
from langchain.tools import StructuredTool
|
||||
from loguru import logger
|
||||
|
||||
|
||||
class NotionPageUpdate(LCToolComponent):
|
||||
display_name: str = "Update Page Property "
|
||||
description: str = "Update the properties of a Notion page."
|
||||
documentation: str = "https://docs.langflow.org/integrations/notion/page-update"
|
||||
icon = "NotionDirectoryLoader"
|
||||
|
||||
inputs = [
|
||||
StrInput(
|
||||
name="page_id",
|
||||
display_name="Page ID",
|
||||
info="The ID of the Notion page to update.",
|
||||
),
|
||||
MultilineInput(
|
||||
name="properties",
|
||||
display_name="Properties",
|
||||
info="The properties to update on the page (as a JSON string or a dictionary).",
|
||||
),
|
||||
SecretStrInput(
|
||||
name="notion_secret",
|
||||
display_name="Notion Secret",
|
||||
info="The Notion integration token.",
|
||||
required=True,
|
||||
),
|
||||
]
|
||||
|
||||
class NotionPageUpdateSchema(BaseModel):
|
||||
page_id: str = Field(..., description="The ID of the Notion page to update.")
|
||||
properties: Union[str, Dict[str, Any]] = Field(
|
||||
..., description="The properties to update on the page (as a JSON string or a dictionary)."
|
||||
)
|
||||
|
||||
def run_model(self) -> Data:
|
||||
result = self._update_notion_page(self.page_id, self.properties)
|
||||
if isinstance(result, str):
|
||||
# An error occurred, return it as text
|
||||
return Data(text=result)
|
||||
else:
|
||||
# Success, return the updated page data
|
||||
output = "Updated page properties:\n"
|
||||
for prop_name, prop_value in result.get("properties", {}).items():
|
||||
output += f"{prop_name}: {prop_value}\n"
|
||||
return Data(text=output, data=result)
|
||||
|
||||
def build_tool(self) -> Tool:
|
||||
return StructuredTool.from_function(
|
||||
name="update_notion_page",
|
||||
description="Update the properties of a Notion page. IMPORTANT: Use the tool to check the Database properties for more details before using this tool.",
|
||||
func=self._update_notion_page,
|
||||
args_schema=self.NotionPageUpdateSchema,
|
||||
)
|
||||
|
||||
def _update_notion_page(self, page_id: str, properties: Union[str, Dict[str, Any]]) -> Union[Dict[str, Any], str]:
|
||||
url = f"https://api.notion.com/v1/pages/{page_id}"
|
||||
headers = {
|
||||
"Authorization": f"Bearer {self.notion_secret}",
|
||||
"Content-Type": "application/json",
|
||||
"Notion-Version": "2022-06-28", # Use the latest supported version
|
||||
}
|
||||
|
||||
# Parse properties if it's a string
|
||||
if isinstance(properties, str):
|
||||
try:
|
||||
parsed_properties = json.loads(properties)
|
||||
except json.JSONDecodeError as e:
|
||||
error_message = f"Invalid JSON format for properties: {str(e)}"
|
||||
logger.error(error_message)
|
||||
return error_message
|
||||
|
||||
else:
|
||||
parsed_properties = properties
|
||||
|
||||
data = {"properties": parsed_properties}
|
||||
|
||||
try:
|
||||
logger.info(f"Sending request to Notion API: URL: {url}, Data: {json.dumps(data)}")
|
||||
response = requests.patch(url, headers=headers, json=data)
|
||||
response.raise_for_status()
|
||||
updated_page = response.json()
|
||||
|
||||
logger.info(f"Successfully updated Notion page. Response: {json.dumps(updated_page)}")
|
||||
return updated_page
|
||||
except requests.exceptions.HTTPError as e:
|
||||
error_message = f"HTTP Error occurred: {str(e)}"
|
||||
if e.response is not None:
|
||||
error_message += f"\nStatus code: {e.response.status_code}"
|
||||
error_message += f"\nResponse body: {e.response.text}"
|
||||
logger.error(error_message)
|
||||
return error_message
|
||||
except requests.exceptions.RequestException as e:
|
||||
error_message = f"An error occurred while making the request: {str(e)}"
|
||||
logger.error(error_message)
|
||||
return error_message
|
||||
except Exception as e:
|
||||
error_message = f"An unexpected error occurred: {str(e)}"
|
||||
logger.error(error_message)
|
||||
return error_message
|
||||
|
||||
def __call__(self, *args, **kwargs):
|
||||
return self._update_notion_page(*args, **kwargs)
|
||||
13
src/backend/base/poetry.lock
generated
13
src/backend/base/poetry.lock
generated
|
|
@ -6315,6 +6315,17 @@ files = [
|
|||
{file = "types_google_cloud_ndb-2.3.0.20240813-py3-none-any.whl", hash = "sha256:79404e04e97324d0b6466f297e92e734a38fb9cd064c2f3816820311bc6c3f57"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "types-markdown"
|
||||
version = "3.7.0.20240822"
|
||||
description = "Typing stubs for Markdown"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "types-Markdown-3.7.0.20240822.tar.gz", hash = "sha256:183557c9f4f865bdefd8f5f96a38145c31819271cde111d35557c3bd2069e78d"},
|
||||
{file = "types_Markdown-3.7.0.20240822-py3-none-any.whl", hash = "sha256:bec91c410aaf2470ffdb103e38438fbcc53689b00133f19e64869eb138432ad7"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "types-passlib"
|
||||
version = "1.7.7.20240327"
|
||||
|
|
@ -7049,4 +7060,4 @@ local = []
|
|||
[metadata]
|
||||
lock-version = "2.0"
|
||||
python-versions = ">=3.10,<3.13"
|
||||
content-hash = "6835a0ed4266b0aae88f40359616174b45cd79515c1d726935060bbb18a106ce"
|
||||
content-hash = "075141462d7ef6e4aff0d55e5fe902e46faec28139556804f1dc3c7ee011d5c9"
|
||||
|
|
|
|||
|
|
@ -119,6 +119,7 @@ dictdiffer = "^0.9.0"
|
|||
pytest-split = "^0.9.0"
|
||||
devtools = "^0.12.2"
|
||||
pytest-flakefinder = "^1.1.0"
|
||||
types-markdown = "^3.7.0.20240822"
|
||||
|
||||
|
||||
[tool.pytest.ini_options]
|
||||
|
|
|
|||
|
|
@ -4,5 +4,6 @@
|
|||
"ENABLE_LANGFLOW_STORE": true,
|
||||
"ENABLE_PROFILE_ICONS": true,
|
||||
"ENABLE_SOCIAL_LINKS": true,
|
||||
"ENABLE_BRANDING": true
|
||||
"ENABLE_BRANDING": true,
|
||||
"ENABLE_MVPS": false
|
||||
}
|
||||
|
|
@ -729,6 +729,8 @@ export const PRIORITY_SIDEBAR_ORDER = [
|
|||
"embeddings",
|
||||
];
|
||||
|
||||
export const BUNDLES_SIDEBAR_FOLDER_NAMES = ["notion"];
|
||||
|
||||
export const AUTHORIZED_DUPLICATE_REQUESTS = [
|
||||
"/health",
|
||||
"/flows",
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import FeatureFlags from "@/../feature-config.json";
|
||||
import { cloneDeep } from "lodash";
|
||||
import { LinkIcon, SparklesIcon } from "lucide-react";
|
||||
import { Fragment, useEffect, useState } from "react";
|
||||
|
|
@ -5,7 +6,10 @@ import IconComponent from "../../../../components/genericIconComponent";
|
|||
import ShadTooltip from "../../../../components/shadTooltipComponent";
|
||||
import { Input } from "../../../../components/ui/input";
|
||||
import { Separator } from "../../../../components/ui/separator";
|
||||
import { PRIORITY_SIDEBAR_ORDER } from "../../../../constants/constants";
|
||||
import {
|
||||
BUNDLES_SIDEBAR_FOLDER_NAMES,
|
||||
PRIORITY_SIDEBAR_ORDER,
|
||||
} from "../../../../constants/constants";
|
||||
import useAlertStore from "../../../../stores/alertStore";
|
||||
import useFlowStore from "../../../../stores/flowStore";
|
||||
import { useTypesStore } from "../../../../stores/typesStore";
|
||||
|
|
@ -408,6 +412,132 @@ export default function ExtraSidebar(): JSX.Element {
|
|||
),
|
||||
)}
|
||||
</ParentDisclosureComponent>
|
||||
{FeatureFlags.ENABLE_MVPS && (
|
||||
<>
|
||||
<Separator />
|
||||
|
||||
<ParentDisclosureComponent
|
||||
defaultOpen={search.length !== 0 || getFilterEdge.length !== 0}
|
||||
key={`${search.length !== 0}-${getFilterEdge.length !== 0}-Bundle`}
|
||||
button={{
|
||||
title: "Bundles",
|
||||
Icon: nodeIconsLucide.unknown,
|
||||
}}
|
||||
testId="extended-disclosure"
|
||||
>
|
||||
{Object.keys(dataFilter)
|
||||
.sort(sortKeys)
|
||||
.filter((x) => BUNDLES_SIDEBAR_FOLDER_NAMES.includes(x))
|
||||
.map((SBSectionName: keyof APIObjectType, index) =>
|
||||
Object.keys(dataFilter[SBSectionName]).length > 0 ? (
|
||||
<Fragment
|
||||
key={`DisclosureComponent${index + search + JSON.stringify(getFilterEdge)}`}
|
||||
>
|
||||
<DisclosureComponent
|
||||
isChild={false}
|
||||
defaultOpen={
|
||||
getFilterEdge.length !== 0 || search.length !== 0
|
||||
? true
|
||||
: false
|
||||
}
|
||||
button={{
|
||||
title: nodeNames[SBSectionName] ?? nodeNames.unknown,
|
||||
Icon:
|
||||
nodeIconsLucide[SBSectionName] ??
|
||||
nodeIconsLucide.unknown,
|
||||
}}
|
||||
>
|
||||
<div className="side-bar-components-gap">
|
||||
{Object.keys(dataFilter[SBSectionName])
|
||||
.sort((a, b) =>
|
||||
sensitiveSort(
|
||||
dataFilter[SBSectionName][a].display_name,
|
||||
dataFilter[SBSectionName][b].display_name,
|
||||
),
|
||||
)
|
||||
.map((SBItemName: string, index) => (
|
||||
<ShadTooltip
|
||||
content={
|
||||
dataFilter[SBSectionName][SBItemName]
|
||||
.display_name
|
||||
}
|
||||
side="right"
|
||||
key={index}
|
||||
>
|
||||
<SidebarDraggableComponent
|
||||
sectionName={SBSectionName as string}
|
||||
apiClass={
|
||||
dataFilter[SBSectionName][SBItemName]
|
||||
}
|
||||
key={index}
|
||||
onDragStart={(event) =>
|
||||
onDragStart(event, {
|
||||
//split type to remove type in nodes saved with same name removing it's
|
||||
type: removeCountFromString(SBItemName),
|
||||
node: dataFilter[SBSectionName][
|
||||
SBItemName
|
||||
],
|
||||
})
|
||||
}
|
||||
color={nodeColors[SBSectionName]}
|
||||
itemName={SBItemName}
|
||||
//convert error to boolean
|
||||
error={
|
||||
!!dataFilter[SBSectionName][SBItemName]
|
||||
.error
|
||||
}
|
||||
display_name={
|
||||
dataFilter[SBSectionName][SBItemName]
|
||||
.display_name
|
||||
}
|
||||
official={
|
||||
dataFilter[SBSectionName][SBItemName]
|
||||
.official === false
|
||||
? false
|
||||
: true
|
||||
}
|
||||
/>
|
||||
</ShadTooltip>
|
||||
))}
|
||||
</div>
|
||||
</DisclosureComponent>
|
||||
{index ===
|
||||
Object.keys(dataFilter).length -
|
||||
PRIORITY_SIDEBAR_ORDER.length +
|
||||
1 && (
|
||||
<>
|
||||
<a
|
||||
target={"_blank"}
|
||||
href="https://langflow.store"
|
||||
className="components-disclosure-arrangement"
|
||||
>
|
||||
<div className="flex gap-4">
|
||||
{/* BUG ON THIS ICON */}
|
||||
<SparklesIcon
|
||||
strokeWidth={1.5}
|
||||
className="w-[22px] text-primary"
|
||||
/>
|
||||
|
||||
<span className="components-disclosure-title">
|
||||
Discover More
|
||||
</span>
|
||||
</div>
|
||||
<div className="components-disclosure-div">
|
||||
<div>
|
||||
<LinkIcon className="h-4 w-4 text-foreground" />
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</>
|
||||
)}
|
||||
</Fragment>
|
||||
) : (
|
||||
<div key={index}></div>
|
||||
),
|
||||
)}
|
||||
</ParentDisclosureComponent>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -283,6 +283,7 @@ export const nodeColors: { [char: string]: string } = {
|
|||
textsplitters: "#B47CB5",
|
||||
toolkits: "#DB2C2C",
|
||||
wrappers: "#E6277A",
|
||||
notion: "#000000",
|
||||
helpers: "#31A3CC",
|
||||
prototypes: "#E6277A",
|
||||
astra_assistants: "#272541",
|
||||
|
|
@ -309,6 +310,7 @@ export const nodeNames: { [char: string]: string } = {
|
|||
data: "Data",
|
||||
prompts: "Prompts",
|
||||
models: "Models",
|
||||
notion: "Notion",
|
||||
model_specs: "Model Specs",
|
||||
chains: "Chains",
|
||||
agents: "Agents",
|
||||
|
|
@ -403,6 +405,7 @@ export const nodeIconsLucide: iconsType = {
|
|||
MongoDBAtlasVectorSearch: MongoDBIcon,
|
||||
MongoDB: MongoDBIcon,
|
||||
MongoDBChatMessageHistory: MongoDBIcon,
|
||||
notion: NotionIcon,
|
||||
NotionDirectoryLoader: NotionIcon,
|
||||
NVIDIA: NvidiaIcon,
|
||||
ChatOpenAI: OpenAiIcon,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue