mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-04-30 19:36:25 +02:00
fix notion write tools and add retry wrapper
This commit is contained in:
parent
8649ca7ac0
commit
04ea40f0c8
4 changed files with 101 additions and 46 deletions
|
|
@ -1,3 +1,4 @@
|
||||||
|
import logging
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from langchain_core.tools import tool
|
from langchain_core.tools import tool
|
||||||
|
|
@ -5,6 +6,8 @@ from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
|
||||||
from app.connectors.notion_history import NotionHistoryConnector
|
from app.connectors.notion_history import NotionHistoryConnector
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def create_create_notion_page_tool(
|
def create_create_notion_page_tool(
|
||||||
db_session: AsyncSession | None = None,
|
db_session: AsyncSession | None = None,
|
||||||
|
|
@ -53,7 +56,10 @@ def create_create_notion_page_tool(
|
||||||
- "Create a Notion page titled 'Meeting Notes' with content 'Discussed project timeline'"
|
- "Create a Notion page titled 'Meeting Notes' with content 'Discussed project timeline'"
|
||||||
- "Save this to Notion with title 'Research Summary'"
|
- "Save this to Notion with title 'Research Summary'"
|
||||||
"""
|
"""
|
||||||
|
logger.info(f"create_notion_page called: title='{title}', parent_page_id={parent_page_id}")
|
||||||
|
|
||||||
if db_session is None or search_space_id is None:
|
if db_session is None or search_space_id is None:
|
||||||
|
logger.error("Notion tool not properly configured - missing db_session or search_space_id")
|
||||||
return {
|
return {
|
||||||
"status": "error",
|
"status": "error",
|
||||||
"message": "Notion tool not properly configured. Please contact support.",
|
"message": "Notion tool not properly configured. Please contact support.",
|
||||||
|
|
@ -61,7 +67,8 @@ def create_create_notion_page_tool(
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Get connector ID if not provided
|
# Get connector ID if not provided
|
||||||
if connector_id is None:
|
actual_connector_id = connector_id
|
||||||
|
if actual_connector_id is None:
|
||||||
from sqlalchemy.future import select
|
from sqlalchemy.future import select
|
||||||
|
|
||||||
from app.db import SearchSourceConnector, SearchSourceConnectorType
|
from app.db import SearchSourceConnector, SearchSourceConnectorType
|
||||||
|
|
@ -71,37 +78,41 @@ def create_create_notion_page_tool(
|
||||||
SearchSourceConnector.search_space_id == search_space_id,
|
SearchSourceConnector.search_space_id == search_space_id,
|
||||||
SearchSourceConnector.connector_type
|
SearchSourceConnector.connector_type
|
||||||
== SearchSourceConnectorType.NOTION_CONNECTOR,
|
== SearchSourceConnectorType.NOTION_CONNECTOR,
|
||||||
SearchSourceConnector.is_enabled == True,
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
connector = result.scalars().first()
|
connector = result.scalars().first()
|
||||||
|
|
||||||
if not connector:
|
if not connector:
|
||||||
|
logger.warning(f"No Notion connector found for search_space_id={search_space_id}")
|
||||||
return {
|
return {
|
||||||
"status": "error",
|
"status": "error",
|
||||||
"message": "No Notion connector found. Please connect Notion in your workspace settings.",
|
"message": "No Notion connector found. Please connect Notion in your workspace settings.",
|
||||||
}
|
}
|
||||||
|
|
||||||
connector_id = connector.id
|
actual_connector_id = connector.id
|
||||||
|
logger.info(f"Found Notion connector: id={actual_connector_id}")
|
||||||
|
|
||||||
# Create connector instance
|
# Create connector instance
|
||||||
notion_connector = NotionHistoryConnector(
|
notion_connector = NotionHistoryConnector(
|
||||||
session=db_session,
|
session=db_session,
|
||||||
connector_id=connector_id,
|
connector_id=actual_connector_id,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Create the page
|
# Create the page
|
||||||
result = await notion_connector.create_page(
|
result = await notion_connector.create_page(
|
||||||
title=title, content=content, parent_page_id=parent_page_id
|
title=title, content=content, parent_page_id=parent_page_id
|
||||||
)
|
)
|
||||||
|
logger.info(f"create_page result: {result.get('status')} - {result.get('message', '')}")
|
||||||
return result
|
return result
|
||||||
|
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
|
logger.error(f"ValueError creating Notion page: {e}")
|
||||||
return {
|
return {
|
||||||
"status": "error",
|
"status": "error",
|
||||||
"message": str(e),
|
"message": str(e),
|
||||||
}
|
}
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
logger.error(f"Unexpected error creating Notion page: {e}", exc_info=True)
|
||||||
return {
|
return {
|
||||||
"status": "error",
|
"status": "error",
|
||||||
"message": f"Unexpected error creating Notion page: {str(e)}",
|
"message": f"Unexpected error creating Notion page: {str(e)}",
|
||||||
|
|
|
||||||
|
|
@ -65,7 +65,6 @@ def create_delete_notion_page_tool(
|
||||||
SearchSourceConnector.search_space_id == search_space_id,
|
SearchSourceConnector.search_space_id == search_space_id,
|
||||||
SearchSourceConnector.connector_type
|
SearchSourceConnector.connector_type
|
||||||
== SearchSourceConnectorType.NOTION_CONNECTOR,
|
== SearchSourceConnectorType.NOTION_CONNECTOR,
|
||||||
SearchSourceConnector.is_enabled == True,
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
connector = result.scalars().first()
|
connector = result.scalars().first()
|
||||||
|
|
|
||||||
|
|
@ -77,7 +77,6 @@ def create_update_notion_page_tool(
|
||||||
SearchSourceConnector.search_space_id == search_space_id,
|
SearchSourceConnector.search_space_id == search_space_id,
|
||||||
SearchSourceConnector.connector_type
|
SearchSourceConnector.connector_type
|
||||||
== SearchSourceConnectorType.NOTION_CONNECTOR,
|
== SearchSourceConnectorType.NOTION_CONNECTOR,
|
||||||
SearchSourceConnector.is_enabled == True,
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
connector = result.scalars().first()
|
connector = result.scalars().first()
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,6 @@ from sqlalchemy.future import select
|
||||||
|
|
||||||
from app.config import config
|
from app.config import config
|
||||||
from app.db import SearchSourceConnector
|
from app.db import SearchSourceConnector
|
||||||
from app.routes.notion_add_connector_route import refresh_notion_token
|
|
||||||
from app.schemas.notion_auth_credentials import NotionAuthCredentialsBase
|
from app.schemas.notion_auth_credentials import NotionAuthCredentialsBase
|
||||||
from app.utils.oauth_security import TokenEncryption
|
from app.utils.oauth_security import TokenEncryption
|
||||||
|
|
||||||
|
|
@ -219,6 +218,7 @@ class NotionHistoryConnector:
|
||||||
)
|
)
|
||||||
|
|
||||||
# Refresh token
|
# Refresh token
|
||||||
|
from app.routes.notion_add_connector_route import refresh_notion_token
|
||||||
connector = await refresh_notion_token(self._session, connector)
|
connector = await refresh_notion_token(self._session, connector)
|
||||||
|
|
||||||
# Reload credentials after refresh
|
# Reload credentials after refresh
|
||||||
|
|
@ -782,6 +782,34 @@ class NotionHistoryConnector:
|
||||||
# WRITE OPERATIONS (create, update, delete pages)
|
# WRITE OPERATIONS (create, update, delete pages)
|
||||||
# =========================================================================
|
# =========================================================================
|
||||||
|
|
||||||
|
async def _get_first_accessible_parent(self) -> str | None:
|
||||||
|
"""
|
||||||
|
Get the first accessible page ID that can be used as a parent.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Page ID string, or None if no accessible pages found
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
notion = await self._get_client()
|
||||||
|
|
||||||
|
# Search for pages, get most recently edited first
|
||||||
|
response = await self._api_call_with_retry(
|
||||||
|
notion.search,
|
||||||
|
filter={"property": "object", "value": "page"},
|
||||||
|
sort={"direction": "descending", "timestamp": "last_edited_time"},
|
||||||
|
page_size=1, # We only need the first one
|
||||||
|
)
|
||||||
|
|
||||||
|
results = response.get("results", [])
|
||||||
|
if results:
|
||||||
|
return results[0]["id"]
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error finding accessible parent page: {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
def _markdown_to_blocks(self, markdown: str) -> list[dict[str, Any]]:
|
def _markdown_to_blocks(self, markdown: str) -> list[dict[str, Any]]:
|
||||||
"""
|
"""
|
||||||
Convert markdown content to Notion blocks.
|
Convert markdown content to Notion blocks.
|
||||||
|
|
@ -884,40 +912,40 @@ class NotionHistoryConnector:
|
||||||
APIResponseError: If Notion API returns an error
|
APIResponseError: If Notion API returns an error
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
|
logger.info(f"Creating Notion page: title='{title}', parent_page_id={parent_page_id}")
|
||||||
|
|
||||||
# Get Notion client
|
# Get Notion client
|
||||||
notion = await self._get_notion_client()
|
notion = await self._get_client()
|
||||||
|
|
||||||
# Convert markdown content to Notion blocks
|
# Convert markdown content to Notion blocks
|
||||||
children = self._markdown_to_blocks(content)
|
children = self._markdown_to_blocks(content)
|
||||||
|
|
||||||
# Prepare parent
|
# Prepare parent - find first available page if not provided
|
||||||
if parent_page_id:
|
if not parent_page_id:
|
||||||
parent = {"type": "page_id", "page_id": parent_page_id}
|
logger.info("No parent_page_id provided, searching for first accessible page...")
|
||||||
else:
|
parent_page_id = await self._get_first_accessible_parent()
|
||||||
# Try to use workspace root
|
if not parent_page_id:
|
||||||
# Note: This requires proper permissions
|
logger.warning("No accessible parent pages found")
|
||||||
result = await self._session.execute(
|
return {
|
||||||
select(SearchSourceConnector).filter(
|
"status": "error",
|
||||||
SearchSourceConnector.id == self._connector_id
|
"message": "Could not find any accessible Notion pages to use as parent. "
|
||||||
)
|
"Please make sure your Notion integration has access to at least one page.",
|
||||||
)
|
|
||||||
connector = result.scalars().first()
|
|
||||||
if connector and connector.config.get("workspace_id"):
|
|
||||||
parent = {"type": "workspace", "workspace": True}
|
|
||||||
else:
|
|
||||||
raise ValueError(
|
|
||||||
"parent_page_id is required. "
|
|
||||||
"Please specify a parent page where the new page should be created."
|
|
||||||
)
|
|
||||||
|
|
||||||
# Create the page
|
|
||||||
response = await notion.pages.create(
|
|
||||||
parent=parent,
|
|
||||||
properties={
|
|
||||||
"title": {
|
|
||||||
"title": [{"type": "text", "text": {"content": title}}]
|
|
||||||
}
|
}
|
||||||
},
|
logger.info(f"Using parent_page_id: {parent_page_id}")
|
||||||
|
|
||||||
|
parent = {"type": "page_id", "page_id": parent_page_id}
|
||||||
|
|
||||||
|
# Create the page with standard title property
|
||||||
|
properties = {
|
||||||
|
"title": {
|
||||||
|
"title": [{"type": "text", "text": {"content": title}}]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
response = await self._api_call_with_retry(
|
||||||
|
notion.pages.create,
|
||||||
|
parent=parent,
|
||||||
|
properties=properties,
|
||||||
children=children[:100], # Notion API limit: 100 blocks per request
|
children=children[:100], # Notion API limit: 100 blocks per request
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -928,8 +956,10 @@ class NotionHistoryConnector:
|
||||||
if len(children) > 100:
|
if len(children) > 100:
|
||||||
for i in range(100, len(children), 100):
|
for i in range(100, len(children), 100):
|
||||||
batch = children[i : i + 100]
|
batch = children[i : i + 100]
|
||||||
await notion.blocks.children.append(
|
await self._api_call_with_retry(
|
||||||
block_id=page_id, children=batch
|
notion.blocks.children.append,
|
||||||
|
block_id=page_id,
|
||||||
|
children=batch
|
||||||
)
|
)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
@ -972,11 +1002,12 @@ class NotionHistoryConnector:
|
||||||
APIResponseError: If Notion API returns an error
|
APIResponseError: If Notion API returns an error
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
notion = await self._get_notion_client()
|
notion = await self._get_client()
|
||||||
|
|
||||||
# Update title if provided
|
# Update title if provided
|
||||||
if title:
|
if title:
|
||||||
await notion.pages.update(
|
await self._api_call_with_retry(
|
||||||
|
notion.pages.update,
|
||||||
page_id=page_id,
|
page_id=page_id,
|
||||||
properties={
|
properties={
|
||||||
"title": {
|
"title": {
|
||||||
|
|
@ -988,22 +1019,33 @@ class NotionHistoryConnector:
|
||||||
# Update content if provided
|
# Update content if provided
|
||||||
if content:
|
if content:
|
||||||
# First, get existing blocks
|
# First, get existing blocks
|
||||||
existing_blocks = await notion.blocks.children.list(block_id=page_id)
|
existing_blocks = await self._api_call_with_retry(
|
||||||
|
notion.blocks.children.list,
|
||||||
|
block_id=page_id
|
||||||
|
)
|
||||||
|
|
||||||
# Delete existing blocks
|
# Delete existing blocks
|
||||||
for block in existing_blocks.get("results", []):
|
for block in existing_blocks.get("results", []):
|
||||||
await notion.blocks.delete(block_id=block["id"])
|
await self._api_call_with_retry(
|
||||||
|
notion.blocks.delete,
|
||||||
|
block_id=block["id"]
|
||||||
|
)
|
||||||
|
|
||||||
# Add new content
|
# Add new content
|
||||||
children = self._markdown_to_blocks(content)
|
children = self._markdown_to_blocks(content)
|
||||||
for i in range(0, len(children), 100):
|
for i in range(0, len(children), 100):
|
||||||
batch = children[i : i + 100]
|
batch = children[i : i + 100]
|
||||||
await notion.blocks.children.append(
|
await self._api_call_with_retry(
|
||||||
block_id=page_id, children=batch
|
notion.blocks.children.append,
|
||||||
|
block_id=page_id,
|
||||||
|
children=batch
|
||||||
)
|
)
|
||||||
|
|
||||||
# Get updated page
|
# Get updated page
|
||||||
response = await notion.pages.retrieve(page_id=page_id)
|
response = await self._api_call_with_retry(
|
||||||
|
notion.pages.retrieve,
|
||||||
|
page_id=page_id
|
||||||
|
)
|
||||||
page_url = response["url"]
|
page_url = response["url"]
|
||||||
page_title = response["properties"]["title"]["title"][0]["text"]["content"]
|
page_title = response["properties"]["title"]["title"][0]["text"]["content"]
|
||||||
|
|
||||||
|
|
@ -1045,10 +1087,14 @@ class NotionHistoryConnector:
|
||||||
APIResponseError: If Notion API returns an error
|
APIResponseError: If Notion API returns an error
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
notion = await self._get_notion_client()
|
notion = await self._get_client()
|
||||||
|
|
||||||
# Archive the page (Notion's way of "deleting")
|
# Archive the page (Notion's way of "deleting")
|
||||||
response = await notion.pages.update(page_id=page_id, archived=True)
|
response = await self._api_call_with_retry(
|
||||||
|
notion.pages.update,
|
||||||
|
page_id=page_id,
|
||||||
|
archived=True
|
||||||
|
)
|
||||||
|
|
||||||
page_title = "Unknown"
|
page_title = "Unknown"
|
||||||
try:
|
try:
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue