diff --git a/surfsense_backend/app/agents/new_chat/tools/create_notion_page.py b/surfsense_backend/app/agents/new_chat/tools/create_notion_page.py deleted file mode 100644 index bbbab81c3..000000000 --- a/surfsense_backend/app/agents/new_chat/tools/create_notion_page.py +++ /dev/null @@ -1,38 +0,0 @@ -import hashlib -from typing import Any - -from langchain_core.tools import tool - - -def create_create_notion_page_tool(): - @tool - async def create_notion_page( - title: str, - content: str, - ) -> dict[str, Any]: - """Create a new page in Notion with the given title and content. - - Use this tool when the user asks you to create, save, or publish - something to Notion. The page will be created in the user's - configured Notion workspace. - - Args: - title: The title of the Notion page. - content: The markdown content for the page body. - """ - # Generate a unique page ID based on title for testing - # This helps verify if edited args were used - page_hash = hashlib.md5(title.encode()).hexdigest()[:8] - - # Return detailed response showing what was actually received - return { - "status": "success", - "page_id": f"stub-page-{page_hash}", - "title": title, - "content_preview": content[:100] + "..." if len(content) > 100 else content, - "content_length": len(content), - "url": f"https://www.notion.so/stub-page-{page_hash}", - "message": f"✅ Created Notion page '{title}' with {len(content)} characters", - } - - return create_notion_page diff --git a/surfsense_backend/app/agents/new_chat/tools/notion/__init__.py b/surfsense_backend/app/agents/new_chat/tools/notion/__init__.py new file mode 100644 index 000000000..97d66b84b --- /dev/null +++ b/surfsense_backend/app/agents/new_chat/tools/notion/__init__.py @@ -0,0 +1,11 @@ +"""Notion tools for creating, updating, and deleting pages.""" + +from .create_page import create_create_notion_page_tool +from .delete_page import create_delete_notion_page_tool +from .update_page import create_update_notion_page_tool + +__all__ = [ + "create_create_notion_page_tool", + "create_update_notion_page_tool", + "create_delete_notion_page_tool", +] diff --git a/surfsense_backend/app/agents/new_chat/tools/notion/create_page.py b/surfsense_backend/app/agents/new_chat/tools/notion/create_page.py new file mode 100644 index 000000000..0f4ea1784 --- /dev/null +++ b/surfsense_backend/app/agents/new_chat/tools/notion/create_page.py @@ -0,0 +1,110 @@ +from typing import Any + +from langchain_core.tools import tool +from sqlalchemy.ext.asyncio import AsyncSession + +from app.connectors.notion_history import NotionHistoryConnector + + +def create_create_notion_page_tool( + db_session: AsyncSession | None = None, + search_space_id: int | None = None, + connector_id: int | None = None, +): + """ + Factory function to create the create_notion_page tool. + + Args: + db_session: Database session for accessing Notion connector + search_space_id: Search space ID to find the Notion connector + connector_id: Optional specific connector ID (if known) + + Returns: + Configured create_notion_page tool + """ + + @tool + async def create_notion_page( + title: str, + content: str, + parent_page_id: str | None = None, + ) -> dict[str, Any]: + """Create a new page in Notion with the given title and content. + + Use this tool when the user asks you to create, save, or publish + something to Notion. The page will be created in the user's + configured Notion workspace. + + Args: + title: The title of the Notion page. + content: The markdown content for the page body (supports headings, lists, paragraphs). + parent_page_id: Optional parent page ID to create as a subpage. + If not provided, will ask for one. + + Returns: + Dictionary with: + - status: "success" or "error" + - page_id: Created page ID + - url: URL to the created page + - title: Page title + - message: Success or error message + + Examples: + - "Create a Notion page titled 'Meeting Notes' with content 'Discussed project timeline'" + - "Save this to Notion with title 'Research Summary'" + """ + if db_session is None or search_space_id is None: + return { + "status": "error", + "message": "Notion tool not properly configured. Please contact support.", + } + + try: + # Get connector ID if not provided + if connector_id is None: + from sqlalchemy.future import select + + from app.db import SearchSourceConnector, SearchSourceConnectorType + + result = await db_session.execute( + select(SearchSourceConnector).filter( + SearchSourceConnector.search_space_id == search_space_id, + SearchSourceConnector.connector_type + == SearchSourceConnectorType.NOTION_CONNECTOR, + SearchSourceConnector.is_enabled == True, + ) + ) + connector = result.scalars().first() + + if not connector: + return { + "status": "error", + "message": "No Notion connector found. Please connect Notion in your workspace settings.", + } + + connector_id = connector.id + + # Create connector instance + notion_connector = NotionHistoryConnector( + session=db_session, + connector_id=connector_id, + ) + + # Create the page + result = await notion_connector.create_page( + title=title, content=content, parent_page_id=parent_page_id + ) + return result + + except ValueError as e: + return { + "status": "error", + "message": str(e), + } + except Exception as e: + return { + "status": "error", + "message": f"Unexpected error creating Notion page: {str(e)}", + } + + return create_notion_page diff --git a/surfsense_backend/app/agents/new_chat/tools/notion/delete_page.py b/surfsense_backend/app/agents/new_chat/tools/notion/delete_page.py new file mode 100644 index 000000000..18cc21da7 --- /dev/null +++ b/surfsense_backend/app/agents/new_chat/tools/notion/delete_page.py @@ -0,0 +1,102 @@ +from typing import Any + +from langchain_core.tools import tool +from sqlalchemy.ext.asyncio import AsyncSession + +from app.connectors.notion_history import NotionHistoryConnector + + +def create_delete_notion_page_tool( + db_session: AsyncSession | None = None, + search_space_id: int | None = None, + connector_id: int | None = None, +): + """ + Factory function to create the delete_notion_page tool. + + Args: + db_session: Database session for accessing Notion connector + search_space_id: Search space ID to find the Notion connector + connector_id: Optional specific connector ID (if known) + + Returns: + Configured delete_notion_page tool + """ + + @tool + async def delete_notion_page( + page_id: str, + ) -> dict[str, Any]: + """Delete (archive) a Notion page. + + Use this tool when the user asks you to delete, remove, or archive + a Notion page. Note that Notion doesn't permanently delete pages, + it archives them (they can be restored from trash). + + Args: + page_id: The ID of the Notion page to delete (required). + + Returns: + Dictionary with: + - status: "success" or "error" + - page_id: Deleted page ID + - message: Success or error message + + Examples: + - "Delete the Notion page abc123" + - "Remove the page xyz789 from Notion" + - "Archive this Notion page: abc123" + """ + if db_session is None or search_space_id is None: + return { + "status": "error", + "message": "Notion tool not properly configured. Please contact support.", + } + + try: + # Get connector ID if not provided + if connector_id is None: + from sqlalchemy.future import select + + from app.db import SearchSourceConnector, SearchSourceConnectorType + + result = await db_session.execute( + select(SearchSourceConnector).filter( + SearchSourceConnector.search_space_id == search_space_id, + SearchSourceConnector.connector_type + == SearchSourceConnectorType.NOTION_CONNECTOR, + SearchSourceConnector.is_enabled == True, + ) + ) + connector = result.scalars().first() + + if not connector: + return { + "status": "error", + "message": "No Notion connector found. Please connect Notion in your workspace settings.", + } + + connector_id = connector.id + + # Create connector instance + notion_connector = NotionHistoryConnector( + session=db_session, + connector_id=connector_id, + ) + + # Delete the page + result = await notion_connector.delete_page(page_id=page_id) + return result + + except ValueError as e: + return { + "status": "error", + "message": str(e), + } + except Exception as e: + return { + "status": "error", + "message": f"Unexpected error deleting Notion page: {str(e)}", + } + + return delete_notion_page diff --git a/surfsense_backend/app/agents/new_chat/tools/notion/update_page.py b/surfsense_backend/app/agents/new_chat/tools/notion/update_page.py new file mode 100644 index 000000000..4c23363e2 --- /dev/null +++ b/surfsense_backend/app/agents/new_chat/tools/notion/update_page.py @@ -0,0 +1,116 @@ +from typing import Any + +from langchain_core.tools import tool +from sqlalchemy.ext.asyncio import AsyncSession + +from app.connectors.notion_history import NotionHistoryConnector + + +def create_update_notion_page_tool( + db_session: AsyncSession | None = None, + search_space_id: int | None = None, + connector_id: int | None = None, +): + """ + Factory function to create the update_notion_page tool. + + Args: + db_session: Database session for accessing Notion connector + search_space_id: Search space ID to find the Notion connector + connector_id: Optional specific connector ID (if known) + + Returns: + Configured update_notion_page tool + """ + + @tool + async def update_notion_page( + page_id: str, + title: str | None = None, + content: str | None = None, + ) -> dict[str, Any]: + """Update an existing Notion page's title and/or content. + + Use this tool when the user asks you to modify, edit, or update + a Notion page. At least one of title or content must be provided. + + Args: + page_id: The ID of the Notion page to update (required). + title: New title for the page (optional). + content: New markdown content for the page body (optional). + If provided, replaces all existing content. + + Returns: + Dictionary with: + - status: "success" or "error" + - page_id: Updated page ID + - url: URL to the updated page + - title: Current page title + - message: Success or error message + + Examples: + - "Update the Notion page abc123 with title 'Updated Meeting Notes'" + - "Change the content of page xyz789 to 'New content here'" + - "Update page abc123 with new title 'Final Report' and content '# Summary...'" + """ + if db_session is None or search_space_id is None: + return { + "status": "error", + "message": "Notion tool not properly configured. Please contact support.", + } + + if not title and not content: + return { + "status": "error", + "message": "At least one of 'title' or 'content' must be provided to update the page.", + } + + try: + # Get connector ID if not provided + if connector_id is None: + from sqlalchemy.future import select + + from app.db import SearchSourceConnector, SearchSourceConnectorType + + result = await db_session.execute( + select(SearchSourceConnector).filter( + SearchSourceConnector.search_space_id == search_space_id, + SearchSourceConnector.connector_type + == SearchSourceConnectorType.NOTION_CONNECTOR, + SearchSourceConnector.is_enabled == True, + ) + ) + connector = result.scalars().first() + + if not connector: + return { + "status": "error", + "message": "No Notion connector found. Please connect Notion in your workspace settings.", + } + + connector_id = connector.id + + # Create connector instance + notion_connector = NotionHistoryConnector( + session=db_session, + connector_id=connector_id, + ) + + # Update the page + result = await notion_connector.update_page( + page_id=page_id, title=title, content=content + ) + return result + + except ValueError as e: + return { + "status": "error", + "message": str(e), + } + except Exception as e: + return { + "status": "error", + "message": f"Unexpected error updating Notion page: {str(e)}", + } + + return update_notion_page diff --git a/surfsense_backend/app/agents/new_chat/tools/registry.py b/surfsense_backend/app/agents/new_chat/tools/registry.py index 3716a19b6..5e61cd7b8 100644 --- a/surfsense_backend/app/agents/new_chat/tools/registry.py +++ b/surfsense_backend/app/agents/new_chat/tools/registry.py @@ -45,12 +45,16 @@ from langchain_core.tools import BaseTool from app.db import ChatVisibility -from .create_notion_page import create_create_notion_page_tool from .display_image import create_display_image_tool from .generate_image import create_generate_image_tool from .knowledge_base import create_search_knowledge_base_tool from .link_preview import create_link_preview_tool from .mcp_tool import load_mcp_tools +from .notion import ( + create_create_notion_page_tool, + create_delete_notion_page_tool, + create_update_notion_page_tool, +) from .podcast import create_generate_podcast_tool from .scrape_webpage import create_scrape_webpage_tool from .search_surfsense_docs import create_search_surfsense_docs_tool @@ -201,13 +205,34 @@ BUILTIN_TOOLS: list[ToolDefinition] = [ requires=["user_id", "search_space_id", "db_session", "thread_visibility"], ), # ========================================================================= - # ADD YOUR CUSTOM TOOLS BELOW + # NOTION TOOLS - create, update, delete pages # ========================================================================= ToolDefinition( name="create_notion_page", description="Create a new page in the user's Notion workspace", - factory=lambda deps: create_create_notion_page_tool(), - requires=[], + factory=lambda deps: create_create_notion_page_tool( + db_session=deps["db_session"], + search_space_id=deps["search_space_id"], + ), + requires=["db_session", "search_space_id"], + ), + ToolDefinition( + name="update_notion_page", + description="Update an existing Notion page's title or content", + factory=lambda deps: create_update_notion_page_tool( + db_session=deps["db_session"], + search_space_id=deps["search_space_id"], + ), + requires=["db_session", "search_space_id"], + ), + ToolDefinition( + name="delete_notion_page", + description="Delete (archive) a Notion page", + factory=lambda deps: create_delete_notion_page_tool( + db_session=deps["db_session"], + search_space_id=deps["search_space_id"], + ), + requires=["db_session", "search_space_id"], ), ]