mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-06-24 21:38:09 +02:00
feat: add Composio connector types and enhance integration
- Introduced new enum values for Composio connectors: COMPOSIO_GOOGLE_DRIVE_CONNECTOR, COMPOSIO_GMAIL_CONNECTOR, and COMPOSIO_GOOGLE_CALENDAR_CONNECTOR. - Updated database migration to add these new enum values to the relevant types. - Refactored Composio integration logic to handle specific connector types, improving the management of connected accounts and indexing processes. - Enhanced frontend components to support the new Composio connector types, including updated UI elements and connector configuration handling. - Improved backend services to manage Composio connected accounts more effectively, including deletion and indexing tasks.
This commit is contained in:
parent
3a1fa25a6f
commit
be5715cfeb
19 changed files with 437 additions and 277 deletions
|
|
@ -1,16 +1,21 @@
|
||||||
"""Add COMPOSIO_CONNECTOR to SearchSourceConnectorType and DocumentType enums
|
"""Add Composio connector types to SearchSourceConnectorType and DocumentType enums
|
||||||
|
|
||||||
Revision ID: 74
|
Revision ID: 74
|
||||||
Revises: 73
|
Revises: 73
|
||||||
Create Date: 2026-01-21
|
Create Date: 2026-01-21
|
||||||
|
|
||||||
This migration adds the COMPOSIO_CONNECTOR enum value to both:
|
This migration adds the Composio connector enum values to both:
|
||||||
- searchsourceconnectortype (for connector type tracking)
|
- searchsourceconnectortype (for connector type tracking)
|
||||||
- documenttype (for document type tracking)
|
- documenttype (for document type tracking)
|
||||||
|
|
||||||
Composio is a managed OAuth integration service that allows connecting
|
Composio is a managed OAuth integration service that allows connecting
|
||||||
to various third-party services (Google Drive, Gmail, Calendar, etc.)
|
to various third-party services (Google Drive, Gmail, Calendar, etc.)
|
||||||
without requiring separate OAuth app verification.
|
without requiring separate OAuth app verification.
|
||||||
|
|
||||||
|
This migration adds three specific connector types:
|
||||||
|
- COMPOSIO_GOOGLE_DRIVE_CONNECTOR
|
||||||
|
- COMPOSIO_GMAIL_CONNECTOR
|
||||||
|
- COMPOSIO_GOOGLE_CALENDAR_CONNECTOR
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from collections.abc import Sequence
|
from collections.abc import Sequence
|
||||||
|
|
@ -23,55 +28,65 @@ down_revision: str | None = "73"
|
||||||
branch_labels: str | Sequence[str] | None = None
|
branch_labels: str | Sequence[str] | None = None
|
||||||
depends_on: str | Sequence[str] | None = None
|
depends_on: str | Sequence[str] | None = None
|
||||||
|
|
||||||
# Define the ENUM type names and the new value
|
# Define the ENUM type names and the new values
|
||||||
CONNECTOR_ENUM = "searchsourceconnectortype"
|
CONNECTOR_ENUM = "searchsourceconnectortype"
|
||||||
CONNECTOR_NEW_VALUE = "COMPOSIO_CONNECTOR"
|
CONNECTOR_NEW_VALUES = [
|
||||||
|
"COMPOSIO_GOOGLE_DRIVE_CONNECTOR",
|
||||||
|
"COMPOSIO_GMAIL_CONNECTOR",
|
||||||
|
"COMPOSIO_GOOGLE_CALENDAR_CONNECTOR",
|
||||||
|
]
|
||||||
DOCUMENT_ENUM = "documenttype"
|
DOCUMENT_ENUM = "documenttype"
|
||||||
DOCUMENT_NEW_VALUE = "COMPOSIO_CONNECTOR"
|
DOCUMENT_NEW_VALUES = [
|
||||||
|
"COMPOSIO_GOOGLE_DRIVE_CONNECTOR",
|
||||||
|
"COMPOSIO_GMAIL_CONNECTOR",
|
||||||
|
"COMPOSIO_GOOGLE_CALENDAR_CONNECTOR",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
def upgrade() -> None:
|
def upgrade() -> None:
|
||||||
"""Upgrade schema - add COMPOSIO_CONNECTOR to connector and document enums safely."""
|
"""Upgrade schema - add Composio connector types to connector and document enums safely."""
|
||||||
# Add COMPOSIO_CONNECTOR to searchsourceconnectortype only if not exists
|
# Add each Composio connector type to searchsourceconnectortype only if not exists
|
||||||
op.execute(
|
for value in CONNECTOR_NEW_VALUES:
|
||||||
f"""
|
op.execute(
|
||||||
DO $$
|
f"""
|
||||||
BEGIN
|
DO $$
|
||||||
IF NOT EXISTS (
|
BEGIN
|
||||||
SELECT 1 FROM pg_enum
|
IF NOT EXISTS (
|
||||||
WHERE enumlabel = '{CONNECTOR_NEW_VALUE}'
|
SELECT 1 FROM pg_enum e
|
||||||
AND enumtypid = (SELECT oid FROM pg_type WHERE typname = '{CONNECTOR_ENUM}')
|
JOIN pg_type t ON e.enumtypid = t.oid
|
||||||
) THEN
|
WHERE t.typname = '{CONNECTOR_ENUM}' AND e.enumlabel = '{value}'
|
||||||
ALTER TYPE {CONNECTOR_ENUM} ADD VALUE '{CONNECTOR_NEW_VALUE}';
|
) THEN
|
||||||
END IF;
|
ALTER TYPE {CONNECTOR_ENUM} ADD VALUE '{value}';
|
||||||
END$$;
|
END IF;
|
||||||
"""
|
END$$;
|
||||||
)
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
# Add COMPOSIO_CONNECTOR to documenttype only if not exists
|
# Add each Composio connector type to documenttype only if not exists
|
||||||
op.execute(
|
for value in DOCUMENT_NEW_VALUES:
|
||||||
f"""
|
op.execute(
|
||||||
DO $$
|
f"""
|
||||||
BEGIN
|
DO $$
|
||||||
IF NOT EXISTS (
|
BEGIN
|
||||||
SELECT 1 FROM pg_enum
|
IF NOT EXISTS (
|
||||||
WHERE enumlabel = '{DOCUMENT_NEW_VALUE}'
|
SELECT 1 FROM pg_enum e
|
||||||
AND enumtypid = (SELECT oid FROM pg_type WHERE typname = '{DOCUMENT_ENUM}')
|
JOIN pg_type t ON e.enumtypid = t.oid
|
||||||
) THEN
|
WHERE t.typname = '{DOCUMENT_ENUM}' AND e.enumlabel = '{value}'
|
||||||
ALTER TYPE {DOCUMENT_ENUM} ADD VALUE '{DOCUMENT_NEW_VALUE}';
|
) THEN
|
||||||
END IF;
|
ALTER TYPE {DOCUMENT_ENUM} ADD VALUE '{value}';
|
||||||
END$$;
|
END IF;
|
||||||
"""
|
END$$;
|
||||||
)
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def downgrade() -> None:
|
def downgrade() -> None:
|
||||||
"""Downgrade schema - remove COMPOSIO_CONNECTOR from connector and document enums.
|
"""Downgrade schema - remove Composio connector types from connector and document enums.
|
||||||
|
|
||||||
Note: PostgreSQL does not support removing enum values directly.
|
Note: PostgreSQL does not support removing enum values directly.
|
||||||
To properly downgrade, you would need to:
|
To properly downgrade, you would need to:
|
||||||
1. Delete any rows using the COMPOSIO_CONNECTOR value
|
1. Delete any rows using the Composio connector type values
|
||||||
2. Create new enums without COMPOSIO_CONNECTOR
|
2. Create new enums without the Composio connector types
|
||||||
3. Alter the columns to use the new enums
|
3. Alter the columns to use the new enums
|
||||||
4. Drop the old enums
|
4. Drop the old enums
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -54,7 +54,9 @@ class DocumentType(str, Enum):
|
||||||
BOOKSTACK_CONNECTOR = "BOOKSTACK_CONNECTOR"
|
BOOKSTACK_CONNECTOR = "BOOKSTACK_CONNECTOR"
|
||||||
CIRCLEBACK = "CIRCLEBACK"
|
CIRCLEBACK = "CIRCLEBACK"
|
||||||
NOTE = "NOTE"
|
NOTE = "NOTE"
|
||||||
COMPOSIO_CONNECTOR = "COMPOSIO_CONNECTOR" # Generic Composio integration
|
COMPOSIO_GOOGLE_DRIVE_CONNECTOR = "COMPOSIO_GOOGLE_DRIVE_CONNECTOR"
|
||||||
|
COMPOSIO_GMAIL_CONNECTOR = "COMPOSIO_GMAIL_CONNECTOR"
|
||||||
|
COMPOSIO_GOOGLE_CALENDAR_CONNECTOR = "COMPOSIO_GOOGLE_CALENDAR_CONNECTOR"
|
||||||
|
|
||||||
|
|
||||||
class SearchSourceConnectorType(str, Enum):
|
class SearchSourceConnectorType(str, Enum):
|
||||||
|
|
@ -82,7 +84,9 @@ class SearchSourceConnectorType(str, Enum):
|
||||||
BOOKSTACK_CONNECTOR = "BOOKSTACK_CONNECTOR"
|
BOOKSTACK_CONNECTOR = "BOOKSTACK_CONNECTOR"
|
||||||
CIRCLEBACK_CONNECTOR = "CIRCLEBACK_CONNECTOR"
|
CIRCLEBACK_CONNECTOR = "CIRCLEBACK_CONNECTOR"
|
||||||
MCP_CONNECTOR = "MCP_CONNECTOR" # Model Context Protocol - User-defined API tools
|
MCP_CONNECTOR = "MCP_CONNECTOR" # Model Context Protocol - User-defined API tools
|
||||||
COMPOSIO_CONNECTOR = "COMPOSIO_CONNECTOR" # Generic Composio integration (Google, Slack, etc.)
|
COMPOSIO_GOOGLE_DRIVE_CONNECTOR = "COMPOSIO_GOOGLE_DRIVE_CONNECTOR"
|
||||||
|
COMPOSIO_GMAIL_CONNECTOR = "COMPOSIO_GMAIL_CONNECTOR"
|
||||||
|
COMPOSIO_GOOGLE_CALENDAR_CONNECTOR = "COMPOSIO_GOOGLE_CALENDAR_CONNECTOR"
|
||||||
|
|
||||||
|
|
||||||
class LiteLLMProvider(str, Enum):
|
class LiteLLMProvider(str, Enum):
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ from fastapi.responses import RedirectResponse
|
||||||
from pydantic import ValidationError
|
from pydantic import ValidationError
|
||||||
from sqlalchemy.exc import IntegrityError
|
from sqlalchemy.exc import IntegrityError
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
from sqlalchemy.future import select
|
||||||
|
|
||||||
from app.config import config
|
from app.config import config
|
||||||
from app.db import (
|
from app.db import (
|
||||||
|
|
@ -30,15 +31,17 @@ from app.db import (
|
||||||
from app.services.composio_service import (
|
from app.services.composio_service import (
|
||||||
COMPOSIO_TOOLKIT_NAMES,
|
COMPOSIO_TOOLKIT_NAMES,
|
||||||
INDEXABLE_TOOLKITS,
|
INDEXABLE_TOOLKITS,
|
||||||
|
TOOLKIT_TO_CONNECTOR_TYPE,
|
||||||
ComposioService,
|
ComposioService,
|
||||||
)
|
)
|
||||||
from app.users import current_active_user
|
from app.users import current_active_user
|
||||||
from app.utils.connector_naming import (
|
from app.utils.connector_naming import generate_unique_connector_name
|
||||||
check_duplicate_connector,
|
|
||||||
generate_unique_connector_name,
|
|
||||||
)
|
|
||||||
from app.utils.oauth_security import OAuthStateManager
|
from app.utils.oauth_security import OAuthStateManager
|
||||||
|
|
||||||
|
# Note: We no longer use check_duplicate_connector for Composio connectors because
|
||||||
|
# Composio generates a new connected_account_id each time, even for the same Google account.
|
||||||
|
# Instead, we check for existing connectors by type/space/user and update them.
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
router = APIRouter()
|
router = APIRouter()
|
||||||
|
|
@ -260,30 +263,65 @@ async def composio_callback(
|
||||||
"is_indexable": toolkit_id in INDEXABLE_TOOLKITS,
|
"is_indexable": toolkit_id in INDEXABLE_TOOLKITS,
|
||||||
}
|
}
|
||||||
|
|
||||||
# Check for duplicate connector
|
# Get the specific connector type for this toolkit
|
||||||
# For Composio, we use toolkit_id + connected_account_id as unique identifier
|
connector_type_str = TOOLKIT_TO_CONNECTOR_TYPE.get(toolkit_id)
|
||||||
identifier = final_connected_account_id or f"{toolkit_id}_{user_id}"
|
if not connector_type_str:
|
||||||
|
raise HTTPException(
|
||||||
is_duplicate = await check_duplicate_connector(
|
status_code=400,
|
||||||
session,
|
detail=f"Unknown toolkit: {toolkit_id}. Available: {list(TOOLKIT_TO_CONNECTOR_TYPE.keys())}",
|
||||||
SearchSourceConnectorType.COMPOSIO_CONNECTOR,
|
|
||||||
space_id,
|
|
||||||
user_id,
|
|
||||||
identifier,
|
|
||||||
)
|
|
||||||
if is_duplicate:
|
|
||||||
logger.warning(
|
|
||||||
f"Duplicate Composio connector detected for user {user_id} with toolkit {toolkit_id}"
|
|
||||||
)
|
)
|
||||||
|
connector_type = SearchSourceConnectorType(connector_type_str)
|
||||||
|
|
||||||
|
# Check for existing connector of the same type for this user/space
|
||||||
|
# When reconnecting, Composio gives a new connected_account_id, so we need to
|
||||||
|
# check by connector_type, user_id, and search_space_id instead of connected_account_id
|
||||||
|
existing_connector_result = await session.execute(
|
||||||
|
select(SearchSourceConnector).where(
|
||||||
|
SearchSourceConnector.connector_type == connector_type,
|
||||||
|
SearchSourceConnector.search_space_id == space_id,
|
||||||
|
SearchSourceConnector.user_id == user_id,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
existing_connector = existing_connector_result.scalars().first()
|
||||||
|
|
||||||
|
if existing_connector:
|
||||||
|
# Delete the old Composio connected account before updating
|
||||||
|
old_connected_account_id = existing_connector.config.get("composio_connected_account_id")
|
||||||
|
if old_connected_account_id and old_connected_account_id != final_connected_account_id:
|
||||||
|
try:
|
||||||
|
deleted = await service.delete_connected_account(old_connected_account_id)
|
||||||
|
if deleted:
|
||||||
|
logger.info(
|
||||||
|
f"Deleted old Composio connected account {old_connected_account_id} "
|
||||||
|
f"before updating connector {existing_connector.id}"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
logger.warning(
|
||||||
|
f"Failed to delete old Composio connected account {old_connected_account_id}"
|
||||||
|
)
|
||||||
|
except Exception as delete_error:
|
||||||
|
# Log but don't fail - the old account may already be deleted
|
||||||
|
logger.warning(
|
||||||
|
f"Error deleting old Composio connected account {old_connected_account_id}: {delete_error!s}"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Update existing connector with new connected_account_id
|
||||||
|
logger.info(
|
||||||
|
f"Updating existing Composio connector {existing_connector.id} with new connected_account_id {final_connected_account_id}"
|
||||||
|
)
|
||||||
|
existing_connector.config = connector_config
|
||||||
|
await session.commit()
|
||||||
|
await session.refresh(existing_connector)
|
||||||
|
|
||||||
return RedirectResponse(
|
return RedirectResponse(
|
||||||
url=f"{config.NEXT_FRONTEND_URL}/dashboard/{space_id}/new-chat?modal=connectors&tab=all&error=duplicate_account&connector=composio-connector"
|
url=f"{config.NEXT_FRONTEND_URL}/dashboard/{space_id}/new-chat?modal=connectors&tab=all&success=true&connector=composio-connector&connectorId={existing_connector.id}"
|
||||||
)
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Generate a unique, user-friendly connector name
|
# Generate a unique, user-friendly connector name
|
||||||
connector_name = await generate_unique_connector_name(
|
connector_name = await generate_unique_connector_name(
|
||||||
session,
|
session,
|
||||||
SearchSourceConnectorType.COMPOSIO_CONNECTOR,
|
connector_type,
|
||||||
space_id,
|
space_id,
|
||||||
user_id,
|
user_id,
|
||||||
f"{toolkit_name} (Composio)",
|
f"{toolkit_name} (Composio)",
|
||||||
|
|
@ -291,7 +329,7 @@ async def composio_callback(
|
||||||
|
|
||||||
db_connector = SearchSourceConnector(
|
db_connector = SearchSourceConnector(
|
||||||
name=connector_name,
|
name=connector_name,
|
||||||
connector_type=SearchSourceConnectorType.COMPOSIO_CONNECTOR,
|
connector_type=connector_type,
|
||||||
config=connector_config,
|
config=connector_config,
|
||||||
search_space_id=space_id,
|
search_space_id=space_id,
|
||||||
user_id=user_id,
|
user_id=user_id,
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,7 @@ from app.db import (
|
||||||
async_session_maker,
|
async_session_maker,
|
||||||
get_async_session,
|
get_async_session,
|
||||||
)
|
)
|
||||||
|
from app.services.composio_service import ComposioService
|
||||||
from app.schemas import (
|
from app.schemas import (
|
||||||
GoogleDriveIndexRequest,
|
GoogleDriveIndexRequest,
|
||||||
MCPConnectorCreate,
|
MCPConnectorCreate,
|
||||||
|
|
@ -529,6 +530,34 @@ async def delete_search_source_connector(
|
||||||
f"Failed to delete periodic schedule for connector {connector_id}"
|
f"Failed to delete periodic schedule for connector {connector_id}"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# For Composio connectors, also delete the connected account in Composio
|
||||||
|
composio_connector_types = [
|
||||||
|
SearchSourceConnectorType.COMPOSIO_GOOGLE_DRIVE_CONNECTOR,
|
||||||
|
SearchSourceConnectorType.COMPOSIO_GMAIL_CONNECTOR,
|
||||||
|
SearchSourceConnectorType.COMPOSIO_GOOGLE_CALENDAR_CONNECTOR,
|
||||||
|
]
|
||||||
|
if db_connector.connector_type in composio_connector_types:
|
||||||
|
composio_connected_account_id = db_connector.config.get("composio_connected_account_id")
|
||||||
|
if composio_connected_account_id and ComposioService.is_enabled():
|
||||||
|
try:
|
||||||
|
service = ComposioService()
|
||||||
|
deleted = await service.delete_connected_account(composio_connected_account_id)
|
||||||
|
if deleted:
|
||||||
|
logger.info(
|
||||||
|
f"Successfully deleted Composio connected account {composio_connected_account_id} "
|
||||||
|
f"for connector {connector_id}"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
logger.warning(
|
||||||
|
f"Failed to delete Composio connected account {composio_connected_account_id} "
|
||||||
|
f"for connector {connector_id}"
|
||||||
|
)
|
||||||
|
except Exception as composio_error:
|
||||||
|
# Log but don't fail the deletion - Composio account may already be deleted
|
||||||
|
logger.warning(
|
||||||
|
f"Error deleting Composio connected account {composio_connected_account_id}: {composio_error!s}"
|
||||||
|
)
|
||||||
|
|
||||||
await session.delete(db_connector)
|
await session.delete(db_connector)
|
||||||
await session.commit()
|
await session.commit()
|
||||||
return {"message": "Search source connector deleted successfully"}
|
return {"message": "Search source connector deleted successfully"}
|
||||||
|
|
@ -868,7 +897,11 @@ async def index_connector_content(
|
||||||
)
|
)
|
||||||
response_message = "Web page indexing started in the background."
|
response_message = "Web page indexing started in the background."
|
||||||
|
|
||||||
elif connector.connector_type == SearchSourceConnectorType.COMPOSIO_CONNECTOR:
|
elif connector.connector_type in [
|
||||||
|
SearchSourceConnectorType.COMPOSIO_GOOGLE_DRIVE_CONNECTOR,
|
||||||
|
SearchSourceConnectorType.COMPOSIO_GMAIL_CONNECTOR,
|
||||||
|
SearchSourceConnectorType.COMPOSIO_GOOGLE_CALENDAR_CONNECTOR,
|
||||||
|
]:
|
||||||
from app.tasks.celery_tasks.connector_tasks import (
|
from app.tasks.celery_tasks.connector_tasks import (
|
||||||
index_composio_connector_task,
|
index_composio_connector_task,
|
||||||
)
|
)
|
||||||
|
|
@ -2086,6 +2119,59 @@ async def run_bookstack_indexing(
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def run_composio_indexing_with_new_session(
|
||||||
|
connector_id: int,
|
||||||
|
search_space_id: int,
|
||||||
|
user_id: str,
|
||||||
|
start_date: str,
|
||||||
|
end_date: str,
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Create a new session and run the Composio indexing task.
|
||||||
|
This prevents session leaks by creating a dedicated session for the background task.
|
||||||
|
"""
|
||||||
|
async with async_session_maker() as session:
|
||||||
|
await run_composio_indexing(
|
||||||
|
session, connector_id, search_space_id, user_id, start_date, end_date
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def run_composio_indexing(
|
||||||
|
session: AsyncSession,
|
||||||
|
connector_id: int,
|
||||||
|
search_space_id: int,
|
||||||
|
user_id: str,
|
||||||
|
start_date: str,
|
||||||
|
end_date: str,
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Run Composio connector indexing with real-time notifications.
|
||||||
|
|
||||||
|
This wraps the Composio indexer with the notification system so that
|
||||||
|
Electric SQL can sync indexing progress to the frontend in real-time.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
session: Database session
|
||||||
|
connector_id: ID of the Composio connector
|
||||||
|
search_space_id: ID of the search space
|
||||||
|
user_id: ID of the user
|
||||||
|
start_date: Start date for indexing
|
||||||
|
end_date: End date for indexing
|
||||||
|
"""
|
||||||
|
from app.tasks.composio_indexer import index_composio_connector
|
||||||
|
|
||||||
|
await _run_indexing_with_notifications(
|
||||||
|
session=session,
|
||||||
|
connector_id=connector_id,
|
||||||
|
search_space_id=search_space_id,
|
||||||
|
user_id=user_id,
|
||||||
|
start_date=start_date,
|
||||||
|
end_date=end_date,
|
||||||
|
indexing_function=index_composio_connector,
|
||||||
|
update_timestamp_func=_update_connector_timestamp_by_id,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
# MCP Connector Routes
|
# MCP Connector Routes
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,20 @@ COMPOSIO_TOOLKIT_NAMES = {
|
||||||
# Toolkits that support indexing (Phase 1: Google services only)
|
# Toolkits that support indexing (Phase 1: Google services only)
|
||||||
INDEXABLE_TOOLKITS = {"googledrive", "gmail", "googlecalendar"}
|
INDEXABLE_TOOLKITS = {"googledrive", "gmail", "googlecalendar"}
|
||||||
|
|
||||||
|
# Mapping of toolkit IDs to connector types
|
||||||
|
TOOLKIT_TO_CONNECTOR_TYPE = {
|
||||||
|
"googledrive": "COMPOSIO_GOOGLE_DRIVE_CONNECTOR",
|
||||||
|
"gmail": "COMPOSIO_GMAIL_CONNECTOR",
|
||||||
|
"googlecalendar": "COMPOSIO_GOOGLE_CALENDAR_CONNECTOR",
|
||||||
|
}
|
||||||
|
|
||||||
|
# Mapping of toolkit IDs to document types
|
||||||
|
TOOLKIT_TO_DOCUMENT_TYPE = {
|
||||||
|
"googledrive": "COMPOSIO_GOOGLE_DRIVE_CONNECTOR",
|
||||||
|
"gmail": "COMPOSIO_GMAIL_CONNECTOR",
|
||||||
|
"googlecalendar": "COMPOSIO_GOOGLE_CALENDAR_CONNECTOR",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class ComposioService:
|
class ComposioService:
|
||||||
"""Service for interacting with Composio API."""
|
"""Service for interacting with Composio API."""
|
||||||
|
|
@ -298,6 +312,26 @@ class ComposioService:
|
||||||
logger.error(f"Failed to list connections for user {user_id}: {e!s}")
|
logger.error(f"Failed to list connections for user {user_id}: {e!s}")
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
async def delete_connected_account(self, connected_account_id: str) -> bool:
|
||||||
|
"""
|
||||||
|
Delete a connected account from Composio.
|
||||||
|
|
||||||
|
This permanently removes the connected account and revokes access tokens.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
connected_account_id: The Composio connected account ID to delete.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
True if deletion was successful, False otherwise.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
self.client.connected_accounts.delete(connected_account_id)
|
||||||
|
logger.info(f"Successfully deleted Composio connected account: {connected_account_id}")
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Failed to delete Composio connected account {connected_account_id}: {e!s}")
|
||||||
|
return False
|
||||||
|
|
||||||
async def execute_tool(
|
async def execute_tool(
|
||||||
self,
|
self,
|
||||||
connected_account_id: str,
|
connected_account_id: str,
|
||||||
|
|
|
||||||
|
|
@ -793,11 +793,13 @@ async def _index_composio_connector(
|
||||||
start_date: str,
|
start_date: str,
|
||||||
end_date: str,
|
end_date: str,
|
||||||
):
|
):
|
||||||
"""Index Composio connector content with new session."""
|
"""Index Composio connector content with new session and real-time notifications."""
|
||||||
# Import from tasks folder (not connector_indexers) to avoid circular import
|
# Import from routes to use the notification-wrapped version
|
||||||
from app.tasks.composio_indexer import index_composio_connector
|
from app.routes.search_source_connectors_routes import (
|
||||||
|
run_composio_indexing,
|
||||||
|
)
|
||||||
|
|
||||||
async with get_celery_session_maker()() as session:
|
async with get_celery_session_maker()() as session:
|
||||||
await index_composio_connector(
|
await run_composio_indexing(
|
||||||
session, connector_id, search_space_id, user_id, start_date, end_date
|
session, connector_id, search_space_id, user_id, start_date, end_date
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ from app.db import (
|
||||||
SearchSourceConnector,
|
SearchSourceConnector,
|
||||||
SearchSourceConnectorType,
|
SearchSourceConnectorType,
|
||||||
)
|
)
|
||||||
from app.services.composio_service import INDEXABLE_TOOLKITS
|
from app.services.composio_service import INDEXABLE_TOOLKITS, TOOLKIT_TO_DOCUMENT_TYPE
|
||||||
from app.services.llm_service import get_user_long_context_llm
|
from app.services.llm_service import get_user_long_context_llm
|
||||||
from app.services.task_logging_service import TaskLoggingService
|
from app.services.task_logging_service import TaskLoggingService
|
||||||
from app.utils.document_converters import (
|
from app.utils.document_converters import (
|
||||||
|
|
@ -58,15 +58,13 @@ async def check_document_by_unique_identifier(
|
||||||
|
|
||||||
|
|
||||||
async def get_connector_by_id(
|
async def get_connector_by_id(
|
||||||
session: AsyncSession, connector_id: int, connector_type: SearchSourceConnectorType
|
session: AsyncSession, connector_id: int, connector_type: SearchSourceConnectorType | None
|
||||||
) -> SearchSourceConnector | None:
|
) -> SearchSourceConnector | None:
|
||||||
"""Get a connector by ID and type from the database."""
|
"""Get a connector by ID and optionally by type from the database."""
|
||||||
result = await session.execute(
|
query = select(SearchSourceConnector).filter(SearchSourceConnector.id == connector_id)
|
||||||
select(SearchSourceConnector).filter(
|
if connector_type is not None:
|
||||||
SearchSourceConnector.id == connector_id,
|
query = query.filter(SearchSourceConnector.connector_type == connector_type)
|
||||||
SearchSourceConnector.connector_type == connector_type,
|
result = await session.execute(query)
|
||||||
)
|
|
||||||
)
|
|
||||||
return result.scalars().first()
|
return result.scalars().first()
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -129,11 +127,24 @@ async def index_composio_connector(
|
||||||
)
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Get connector by id
|
# Get connector by id - accept any Composio connector type
|
||||||
|
# We'll check the actual type after loading
|
||||||
connector = await get_connector_by_id(
|
connector = await get_connector_by_id(
|
||||||
session, connector_id, SearchSourceConnectorType.COMPOSIO_CONNECTOR
|
session, connector_id, None # Don't filter by type, we'll validate after
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Validate it's a Composio connector
|
||||||
|
if connector and connector.connector_type not in [
|
||||||
|
SearchSourceConnectorType.COMPOSIO_GOOGLE_DRIVE_CONNECTOR,
|
||||||
|
SearchSourceConnectorType.COMPOSIO_GMAIL_CONNECTOR,
|
||||||
|
SearchSourceConnectorType.COMPOSIO_GOOGLE_CALENDAR_CONNECTOR,
|
||||||
|
]:
|
||||||
|
error_msg = f"Connector {connector_id} is not a Composio connector"
|
||||||
|
await task_logger.log_task_failure(
|
||||||
|
log_entry, error_msg, {"error_type": "InvalidConnectorType"}
|
||||||
|
)
|
||||||
|
return 0, error_msg
|
||||||
|
|
||||||
if not connector:
|
if not connector:
|
||||||
error_msg = f"Composio connector with ID {connector_id} not found"
|
error_msg = f"Composio connector with ID {connector_id} not found"
|
||||||
await task_logger.log_task_failure(
|
await task_logger.log_task_failure(
|
||||||
|
|
@ -276,7 +287,7 @@ async def _index_composio_google_drive(
|
||||||
await task_logger.log_task_success(
|
await task_logger.log_task_success(
|
||||||
log_entry, success_msg, {"files_count": 0}
|
log_entry, success_msg, {"files_count": 0}
|
||||||
)
|
)
|
||||||
return 0, success_msg
|
return 0, None # Return None (not error) when no items found - this is success with 0 items
|
||||||
|
|
||||||
logger.info(f"Found {len(all_files)} Google Drive files to index via Composio")
|
logger.info(f"Found {len(all_files)} Google Drive files to index via Composio")
|
||||||
|
|
||||||
|
|
@ -299,8 +310,9 @@ async def _index_composio_google_drive(
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Generate unique identifier hash
|
# Generate unique identifier hash
|
||||||
|
document_type = DocumentType(TOOLKIT_TO_DOCUMENT_TYPE["googledrive"])
|
||||||
unique_identifier_hash = generate_unique_identifier_hash(
|
unique_identifier_hash = generate_unique_identifier_hash(
|
||||||
DocumentType.COMPOSIO_CONNECTOR, f"drive_{file_id}", search_space_id
|
document_type, f"drive_{file_id}", search_space_id
|
||||||
)
|
)
|
||||||
|
|
||||||
# Check if document exists
|
# Check if document exists
|
||||||
|
|
@ -394,7 +406,7 @@ async def _index_composio_google_drive(
|
||||||
document = Document(
|
document = Document(
|
||||||
search_space_id=search_space_id,
|
search_space_id=search_space_id,
|
||||||
title=f"Drive: {file_name}",
|
title=f"Drive: {file_name}",
|
||||||
document_type=DocumentType.COMPOSIO_CONNECTOR,
|
document_type=DocumentType(TOOLKIT_TO_DOCUMENT_TYPE["googledrive"]),
|
||||||
document_metadata={
|
document_metadata={
|
||||||
"file_id": file_id,
|
"file_id": file_id,
|
||||||
"file_name": file_name,
|
"file_name": file_name,
|
||||||
|
|
@ -489,7 +501,7 @@ async def _index_composio_gmail(
|
||||||
await task_logger.log_task_success(
|
await task_logger.log_task_success(
|
||||||
log_entry, success_msg, {"messages_count": 0}
|
log_entry, success_msg, {"messages_count": 0}
|
||||||
)
|
)
|
||||||
return 0, success_msg
|
return 0, None # Return None (not error) when no items found - this is success with 0 items
|
||||||
|
|
||||||
logger.info(f"Found {len(messages)} Gmail messages to index via Composio")
|
logger.info(f"Found {len(messages)} Gmail messages to index via Composio")
|
||||||
|
|
||||||
|
|
@ -530,8 +542,9 @@ async def _index_composio_gmail(
|
||||||
markdown_content = composio_connector.format_gmail_message_to_markdown(message)
|
markdown_content = composio_connector.format_gmail_message_to_markdown(message)
|
||||||
|
|
||||||
# Generate unique identifier
|
# Generate unique identifier
|
||||||
|
document_type = DocumentType(TOOLKIT_TO_DOCUMENT_TYPE["gmail"])
|
||||||
unique_identifier_hash = generate_unique_identifier_hash(
|
unique_identifier_hash = generate_unique_identifier_hash(
|
||||||
DocumentType.COMPOSIO_CONNECTOR, f"gmail_{message_id}", search_space_id
|
document_type, f"gmail_{message_id}", search_space_id
|
||||||
)
|
)
|
||||||
|
|
||||||
content_hash = generate_content_hash(markdown_content, search_space_id)
|
content_hash = generate_content_hash(markdown_content, search_space_id)
|
||||||
|
|
@ -612,7 +625,7 @@ async def _index_composio_gmail(
|
||||||
document = Document(
|
document = Document(
|
||||||
search_space_id=search_space_id,
|
search_space_id=search_space_id,
|
||||||
title=f"Gmail: {subject}",
|
title=f"Gmail: {subject}",
|
||||||
document_type=DocumentType.COMPOSIO_CONNECTOR,
|
document_type=DocumentType(TOOLKIT_TO_DOCUMENT_TYPE["gmail"]),
|
||||||
document_metadata={
|
document_metadata={
|
||||||
"message_id": message_id,
|
"message_id": message_id,
|
||||||
"subject": subject,
|
"subject": subject,
|
||||||
|
|
@ -717,7 +730,7 @@ async def _index_composio_google_calendar(
|
||||||
await task_logger.log_task_success(
|
await task_logger.log_task_success(
|
||||||
log_entry, success_msg, {"events_count": 0}
|
log_entry, success_msg, {"events_count": 0}
|
||||||
)
|
)
|
||||||
return 0, success_msg
|
return 0, None # Return None (not error) when no items found - this is success with 0 items
|
||||||
|
|
||||||
logger.info(f"Found {len(events)} Google Calendar events to index via Composio")
|
logger.info(f"Found {len(events)} Google Calendar events to index via Composio")
|
||||||
|
|
||||||
|
|
@ -738,8 +751,9 @@ async def _index_composio_google_calendar(
|
||||||
markdown_content = composio_connector.format_calendar_event_to_markdown(event)
|
markdown_content = composio_connector.format_calendar_event_to_markdown(event)
|
||||||
|
|
||||||
# Generate unique identifier
|
# Generate unique identifier
|
||||||
|
document_type = DocumentType(TOOLKIT_TO_DOCUMENT_TYPE["googlecalendar"])
|
||||||
unique_identifier_hash = generate_unique_identifier_hash(
|
unique_identifier_hash = generate_unique_identifier_hash(
|
||||||
DocumentType.COMPOSIO_CONNECTOR, f"calendar_{event_id}", search_space_id
|
document_type, f"calendar_{event_id}", search_space_id
|
||||||
)
|
)
|
||||||
|
|
||||||
content_hash = generate_content_hash(markdown_content, search_space_id)
|
content_hash = generate_content_hash(markdown_content, search_space_id)
|
||||||
|
|
@ -828,7 +842,7 @@ async def _index_composio_google_calendar(
|
||||||
document = Document(
|
document = Document(
|
||||||
search_space_id=search_space_id,
|
search_space_id=search_space_id,
|
||||||
title=f"Calendar: {summary}",
|
title=f"Calendar: {summary}",
|
||||||
document_type=DocumentType.COMPOSIO_CONNECTOR,
|
document_type=DocumentType(TOOLKIT_TO_DOCUMENT_TYPE["googlecalendar"]),
|
||||||
document_metadata={
|
document_metadata={
|
||||||
"event_id": event_id,
|
"event_id": event_id,
|
||||||
"summary": summary,
|
"summary": summary,
|
||||||
|
|
|
||||||
|
|
@ -188,8 +188,18 @@ export const ConnectorIndicator: FC = () => {
|
||||||
searchSpaceId={searchSpaceId}
|
searchSpaceId={searchSpaceId}
|
||||||
connectedToolkits={
|
connectedToolkits={
|
||||||
(connectors || [])
|
(connectors || [])
|
||||||
.filter((c: SearchSourceConnector) => c.connector_type === "COMPOSIO_CONNECTOR")
|
.filter((c: SearchSourceConnector) =>
|
||||||
.map((c: SearchSourceConnector) => c.config?.toolkit_id as string)
|
c.connector_type === "COMPOSIO_GOOGLE_DRIVE_CONNECTOR" ||
|
||||||
|
c.connector_type === "COMPOSIO_GMAIL_CONNECTOR" ||
|
||||||
|
c.connector_type === "COMPOSIO_GOOGLE_CALENDAR_CONNECTOR"
|
||||||
|
)
|
||||||
|
.map((c: SearchSourceConnector) => {
|
||||||
|
// Map connector type back to toolkit_id
|
||||||
|
if (c.connector_type === "COMPOSIO_GOOGLE_DRIVE_CONNECTOR") return "googledrive";
|
||||||
|
if (c.connector_type === "COMPOSIO_GMAIL_CONNECTOR") return "gmail";
|
||||||
|
if (c.connector_type === "COMPOSIO_GOOGLE_CALENDAR_CONNECTOR") return "googlecalendar";
|
||||||
|
return c.config?.toolkit_id as string;
|
||||||
|
})
|
||||||
.filter(Boolean)
|
.filter(Boolean)
|
||||||
}
|
}
|
||||||
onBack={handleBackFromComposio}
|
onBack={handleBackFromComposio}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,5 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { ExternalLink, Info, Zap } from "lucide-react";
|
|
||||||
import Image from "next/image";
|
|
||||||
import type { FC } from "react";
|
import type { FC } from "react";
|
||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
import type { SearchSourceConnector } from "@/contracts/types/connector.types";
|
import type { SearchSourceConnector } from "@/contracts/types/connector.types";
|
||||||
|
|
@ -13,92 +11,13 @@ interface ComposioConfigProps {
|
||||||
onNameChange?: (name: string) => void;
|
onNameChange?: (name: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get toolkit display info
|
|
||||||
const getToolkitInfo = (toolkitId: string): { name: string; icon: string; description: string } => {
|
|
||||||
switch (toolkitId) {
|
|
||||||
case "googledrive":
|
|
||||||
return {
|
|
||||||
name: "Google Drive",
|
|
||||||
icon: "/connectors/google-drive.svg",
|
|
||||||
description: "Files and documents from Google Drive",
|
|
||||||
};
|
|
||||||
case "gmail":
|
|
||||||
return {
|
|
||||||
name: "Gmail",
|
|
||||||
icon: "/connectors/google-gmail.svg",
|
|
||||||
description: "Emails from Gmail",
|
|
||||||
};
|
|
||||||
case "googlecalendar":
|
|
||||||
return {
|
|
||||||
name: "Google Calendar",
|
|
||||||
icon: "/connectors/google-calendar.svg",
|
|
||||||
description: "Events from Google Calendar",
|
|
||||||
};
|
|
||||||
case "slack":
|
|
||||||
return {
|
|
||||||
name: "Slack",
|
|
||||||
icon: "/connectors/slack.svg",
|
|
||||||
description: "Messages from Slack",
|
|
||||||
};
|
|
||||||
case "notion":
|
|
||||||
return {
|
|
||||||
name: "Notion",
|
|
||||||
icon: "/connectors/notion.svg",
|
|
||||||
description: "Pages from Notion",
|
|
||||||
};
|
|
||||||
case "github":
|
|
||||||
return {
|
|
||||||
name: "GitHub",
|
|
||||||
icon: "/connectors/github.svg",
|
|
||||||
description: "Repositories from GitHub",
|
|
||||||
};
|
|
||||||
default:
|
|
||||||
return {
|
|
||||||
name: toolkitId,
|
|
||||||
icon: "/connectors/composio.svg",
|
|
||||||
description: "Connected via Composio",
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const ComposioConfig: FC<ComposioConfigProps> = ({ connector }) => {
|
export const ComposioConfig: FC<ComposioConfigProps> = ({ connector }) => {
|
||||||
const toolkitId = connector.config?.toolkit_id as string;
|
const toolkitId = connector.config?.toolkit_id as string;
|
||||||
const toolkitName = connector.config?.toolkit_name as string;
|
|
||||||
const isIndexable = connector.config?.is_indexable as boolean;
|
const isIndexable = connector.config?.is_indexable as boolean;
|
||||||
const composioAccountId = connector.config?.composio_connected_account_id as string;
|
const composioAccountId = connector.config?.composio_connected_account_id as string;
|
||||||
|
|
||||||
const toolkitInfo = getToolkitInfo(toolkitId);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
{/* Toolkit Info Card */}
|
|
||||||
<div className="rounded-xl border border-violet-500/20 bg-gradient-to-br from-violet-500/5 to-purple-500/5 p-4">
|
|
||||||
<div className="flex items-start gap-4">
|
|
||||||
<div className="flex h-12 w-12 items-center justify-center rounded-lg bg-gradient-to-br from-violet-500/10 to-purple-500/10 border border-violet-500/20 shrink-0">
|
|
||||||
<Image
|
|
||||||
src={toolkitInfo.icon}
|
|
||||||
alt={toolkitInfo.name}
|
|
||||||
width={24}
|
|
||||||
height={24}
|
|
||||||
className="size-6"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="flex-1 min-w-0">
|
|
||||||
<div className="flex items-center gap-2 mb-1">
|
|
||||||
<h3 className="text-sm font-semibold">{toolkitName || toolkitInfo.name}</h3>
|
|
||||||
<Badge
|
|
||||||
variant="secondary"
|
|
||||||
className="text-[10px] px-1.5 py-0 h-5 bg-violet-500/10 text-violet-600 dark:text-violet-400 border-violet-500/20"
|
|
||||||
>
|
|
||||||
<Zap className="size-3 mr-0.5" />
|
|
||||||
Composio
|
|
||||||
</Badge>
|
|
||||||
</div>
|
|
||||||
<p className="text-xs text-muted-foreground">{toolkitInfo.description}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Connection Details */}
|
{/* Connection Details */}
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
<h4 className="text-xs font-medium text-muted-foreground uppercase tracking-wider">
|
<h4 className="text-xs font-medium text-muted-foreground uppercase tracking-wider">
|
||||||
|
|
@ -133,28 +52,6 @@ export const ComposioConfig: FC<ComposioConfigProps> = ({ connector }) => {
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Info Banner */}
|
|
||||||
<div className="rounded-lg border border-border/50 bg-muted/30 p-3">
|
|
||||||
<div className="flex items-start gap-2.5">
|
|
||||||
<Info className="size-4 text-muted-foreground shrink-0 mt-0.5" />
|
|
||||||
<div className="space-y-1">
|
|
||||||
<p className="text-xs text-muted-foreground leading-relaxed">
|
|
||||||
This connection uses Composio's managed OAuth, which means you don't need to
|
|
||||||
wait for app verification. Your data is securely accessed through Composio.
|
|
||||||
</p>
|
|
||||||
<a
|
|
||||||
href="https://composio.dev"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
className="inline-flex items-center gap-1 text-xs text-violet-600 dark:text-violet-400 hover:underline"
|
|
||||||
>
|
|
||||||
Learn more about Composio
|
|
||||||
<ExternalLink className="size-3" />
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -74,7 +74,9 @@ export function getConnectorConfigComponent(
|
||||||
return CirclebackConfig;
|
return CirclebackConfig;
|
||||||
case "MCP_CONNECTOR":
|
case "MCP_CONNECTOR":
|
||||||
return MCPConfig;
|
return MCPConfig;
|
||||||
case "COMPOSIO_CONNECTOR":
|
case "COMPOSIO_GOOGLE_DRIVE_CONNECTOR":
|
||||||
|
case "COMPOSIO_GMAIL_CONNECTOR":
|
||||||
|
case "COMPOSIO_GOOGLE_CALENDAR_CONNECTOR":
|
||||||
return ComposioConfig;
|
return ComposioConfig;
|
||||||
// OAuth connectors (Gmail, Calendar, Airtable, Notion) and others don't need special config UI
|
// OAuth connectors (Gmail, Calendar, Airtable, Notion) and others don't need special config UI
|
||||||
default:
|
default:
|
||||||
|
|
|
||||||
|
|
@ -168,14 +168,28 @@ export const OTHER_CONNECTORS = [
|
||||||
},
|
},
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
// Composio Connector (Single entry that opens toolkit selector)
|
// Composio Connectors - Individual entries for each supported toolkit
|
||||||
export const COMPOSIO_CONNECTORS = [
|
export const COMPOSIO_CONNECTORS = [
|
||||||
{
|
{
|
||||||
id: "composio-connector",
|
id: "composio-googledrive",
|
||||||
title: "Composio",
|
title: "Google Drive",
|
||||||
description: "Connect 100+ apps via Composio (Google, Slack, Notion, etc.)",
|
description: "Search your Drive files via Composio",
|
||||||
connectorType: EnumConnectorName.COMPOSIO_CONNECTOR,
|
connectorType: EnumConnectorName.COMPOSIO_GOOGLE_DRIVE_CONNECTOR,
|
||||||
// No authEndpoint - handled via toolkit selector view
|
authEndpoint: "/api/v1/auth/composio/connector/add/?toolkit_id=googledrive",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "composio-gmail",
|
||||||
|
title: "Gmail",
|
||||||
|
description: "Search through your emails via Composio",
|
||||||
|
connectorType: EnumConnectorName.COMPOSIO_GMAIL_CONNECTOR,
|
||||||
|
authEndpoint: "/api/v1/auth/composio/connector/add/?toolkit_id=gmail",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "composio-googlecalendar",
|
||||||
|
title: "Google Calendar",
|
||||||
|
description: "Search through your events via Composio",
|
||||||
|
connectorType: EnumConnectorName.COMPOSIO_GOOGLE_CALENDAR_CONNECTOR,
|
||||||
|
authEndpoint: "/api/v1/auth/composio/connector/add/?toolkit_id=googlecalendar",
|
||||||
},
|
},
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ import { searchSourceConnectorTypeEnum } from "@/contracts/types/connector.types
|
||||||
export const connectorPopupQueryParamsSchema = z.object({
|
export const connectorPopupQueryParamsSchema = z.object({
|
||||||
modal: z.enum(["connectors"]).optional(),
|
modal: z.enum(["connectors"]).optional(),
|
||||||
tab: z.enum(["all", "active"]).optional(),
|
tab: z.enum(["all", "active"]).optional(),
|
||||||
view: z.enum(["configure", "edit", "connect", "youtube", "accounts", "mcp-list"]).optional(),
|
view: z.enum(["configure", "edit", "connect", "youtube", "accounts", "mcp-list", "composio"]).optional(),
|
||||||
connector: z.string().optional(),
|
connector: z.string().optional(),
|
||||||
connectorId: z.string().optional(),
|
connectorId: z.string().optional(),
|
||||||
connectorType: z.string().optional(),
|
connectorType: z.string().optional(),
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@ import {
|
||||||
import { cacheKeys } from "@/lib/query-client/cache-keys";
|
import { cacheKeys } from "@/lib/query-client/cache-keys";
|
||||||
import { queryClient } from "@/lib/query-client/client";
|
import { queryClient } from "@/lib/query-client/client";
|
||||||
import type { IndexingConfigState } from "../constants/connector-constants";
|
import type { IndexingConfigState } from "../constants/connector-constants";
|
||||||
import { OAUTH_CONNECTORS, OTHER_CONNECTORS } from "../constants/connector-constants";
|
import { COMPOSIO_CONNECTORS, OAUTH_CONNECTORS, OTHER_CONNECTORS } from "../constants/connector-constants";
|
||||||
import {
|
import {
|
||||||
dateRangeSchema,
|
dateRangeSchema,
|
||||||
frequencyMinutesSchema,
|
frequencyMinutesSchema,
|
||||||
|
|
@ -176,15 +176,24 @@ export const useConnectorDialog = () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle accounts view
|
// Handle accounts view
|
||||||
if (params.view === "accounts" && params.connectorType && !viewingAccountsType) {
|
if (params.view === "accounts" && params.connectorType) {
|
||||||
const oauthConnector = OAUTH_CONNECTORS.find(
|
// Update state if not set, or if connectorType has changed
|
||||||
(c) => c.connectorType === params.connectorType
|
const needsUpdate = !viewingAccountsType ||
|
||||||
);
|
viewingAccountsType.connectorType !== params.connectorType;
|
||||||
if (oauthConnector) {
|
|
||||||
setViewingAccountsType({
|
if (needsUpdate) {
|
||||||
connectorType: oauthConnector.connectorType,
|
// Check both OAUTH_CONNECTORS and COMPOSIO_CONNECTORS
|
||||||
connectorTitle: oauthConnector.title,
|
const oauthConnector = OAUTH_CONNECTORS.find(
|
||||||
});
|
(c) => c.connectorType === params.connectorType
|
||||||
|
) || COMPOSIO_CONNECTORS.find(
|
||||||
|
(c) => c.connectorType === params.connectorType
|
||||||
|
);
|
||||||
|
if (oauthConnector) {
|
||||||
|
setViewingAccountsType({
|
||||||
|
connectorType: oauthConnector.connectorType,
|
||||||
|
connectorTitle: oauthConnector.title,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -293,6 +302,8 @@ export const useConnectorDialog = () => {
|
||||||
indexingConfig,
|
indexingConfig,
|
||||||
connectingConnectorType,
|
connectingConnectorType,
|
||||||
viewingAccountsType,
|
viewingAccountsType,
|
||||||
|
viewingMCPList,
|
||||||
|
viewingComposio,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Detect OAuth success / Failure and transition to config view
|
// Detect OAuth success / Failure and transition to config view
|
||||||
|
|
@ -389,15 +400,19 @@ export const useConnectorDialog = () => {
|
||||||
|
|
||||||
// Handle OAuth connection
|
// Handle OAuth connection
|
||||||
const handleConnectOAuth = useCallback(
|
const handleConnectOAuth = useCallback(
|
||||||
async (connector: (typeof OAUTH_CONNECTORS)[number]) => {
|
async (connector: (typeof OAUTH_CONNECTORS)[number] | (typeof COMPOSIO_CONNECTORS)[number]) => {
|
||||||
if (!searchSpaceId || !connector.authEndpoint) return;
|
if (!searchSpaceId || !connector.authEndpoint) return;
|
||||||
|
|
||||||
// Set connecting state immediately to disable button and show spinner
|
// Set connecting state immediately to disable button and show spinner
|
||||||
setConnectingId(connector.id);
|
setConnectingId(connector.id);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// Check if authEndpoint already has query parameters
|
||||||
|
const separator = connector.authEndpoint.includes("?") ? "&" : "?";
|
||||||
|
const url = `${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}${connector.authEndpoint}${separator}space_id=${searchSpaceId}`;
|
||||||
|
|
||||||
const response = await authenticatedFetch(
|
const response = await authenticatedFetch(
|
||||||
`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}${connector.authEndpoint}?space_id=${searchSpaceId}`,
|
url,
|
||||||
{ method: "GET" }
|
{ method: "GET" }
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -799,23 +814,19 @@ export const useConnectorDialog = () => {
|
||||||
|
|
||||||
// Handle viewing accounts list for OAuth connector type
|
// Handle viewing accounts list for OAuth connector type
|
||||||
const handleViewAccountsList = useCallback(
|
const handleViewAccountsList = useCallback(
|
||||||
(connectorType: string, connectorTitle: string) => {
|
(connectorType: string, _connectorTitle?: string) => {
|
||||||
if (!searchSpaceId) return;
|
if (!searchSpaceId) return;
|
||||||
|
|
||||||
setViewingAccountsType({
|
|
||||||
connectorType,
|
|
||||||
connectorTitle,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Update URL to show accounts view, preserving current tab
|
// Update URL to show accounts view, preserving current tab
|
||||||
|
// The useEffect will handle setting viewingAccountsType based on URL params
|
||||||
const url = new URL(window.location.href);
|
const url = new URL(window.location.href);
|
||||||
url.searchParams.set("modal", "connectors");
|
url.searchParams.set("modal", "connectors");
|
||||||
url.searchParams.set("view", "accounts");
|
url.searchParams.set("view", "accounts");
|
||||||
url.searchParams.set("connectorType", connectorType);
|
url.searchParams.set("connectorType", connectorType);
|
||||||
// Keep the current tab in URL so we can go back to it
|
// Keep the current tab in URL so we can go back to it
|
||||||
window.history.pushState({ modal: true }, "", url.toString());
|
router.replace(url.pathname + url.search, { scroll: false });
|
||||||
},
|
},
|
||||||
[searchSpaceId]
|
[searchSpaceId, router]
|
||||||
);
|
);
|
||||||
|
|
||||||
// Handle going back from accounts list view
|
// Handle going back from accounts list view
|
||||||
|
|
@ -839,8 +850,8 @@ export const useConnectorDialog = () => {
|
||||||
const url = new URL(window.location.href);
|
const url = new URL(window.location.href);
|
||||||
url.searchParams.set("modal", "connectors");
|
url.searchParams.set("modal", "connectors");
|
||||||
url.searchParams.set("view", "mcp-list");
|
url.searchParams.set("view", "mcp-list");
|
||||||
window.history.pushState({ modal: true }, "", url.toString());
|
router.replace(url.pathname + url.search, { scroll: false });
|
||||||
}, [searchSpaceId]);
|
}, [searchSpaceId, router]);
|
||||||
|
|
||||||
// Handle going back from MCP list view
|
// Handle going back from MCP list view
|
||||||
const handleBackFromMCPList = useCallback(() => {
|
const handleBackFromMCPList = useCallback(() => {
|
||||||
|
|
@ -871,8 +882,8 @@ export const useConnectorDialog = () => {
|
||||||
const url = new URL(window.location.href);
|
const url = new URL(window.location.href);
|
||||||
url.searchParams.set("modal", "connectors");
|
url.searchParams.set("modal", "connectors");
|
||||||
url.searchParams.set("view", "composio");
|
url.searchParams.set("view", "composio");
|
||||||
window.history.pushState({ modal: true }, "", url.toString());
|
router.replace(url.pathname + url.search, { scroll: false });
|
||||||
}, [searchSpaceId]);
|
}, [searchSpaceId, router]);
|
||||||
|
|
||||||
// Handle going back from Composio view
|
// Handle going back from Composio view
|
||||||
const handleBackFromComposio = useCallback(() => {
|
const handleBackFromComposio = useCallback(() => {
|
||||||
|
|
@ -1423,7 +1434,7 @@ export const useConnectorDialog = () => {
|
||||||
setIsDisconnecting(false);
|
setIsDisconnecting(false);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[editingConnector, searchSpaceId, deleteConnector, router]
|
[editingConnector, searchSpaceId, deleteConnector, router, cameFromMCPList]
|
||||||
);
|
);
|
||||||
|
|
||||||
// Handle quick index (index without date picker, uses backend defaults)
|
// Handle quick index (index without date picker, uses backend defaults)
|
||||||
|
|
@ -1579,6 +1590,7 @@ export const useConnectorDialog = () => {
|
||||||
viewingAccountsType,
|
viewingAccountsType,
|
||||||
viewingMCPList,
|
viewingMCPList,
|
||||||
viewingComposio,
|
viewingComposio,
|
||||||
|
connectingComposioToolkit,
|
||||||
|
|
||||||
// Setters
|
// Setters
|
||||||
setSearchQuery,
|
setSearchQuery,
|
||||||
|
|
@ -1616,8 +1628,6 @@ export const useConnectorDialog = () => {
|
||||||
setIndexingConnectorConfig,
|
setIndexingConnectorConfig,
|
||||||
|
|
||||||
// Composio
|
// Composio
|
||||||
viewingComposio,
|
|
||||||
connectingComposioToolkit,
|
|
||||||
handleOpenComposio,
|
handleOpenComposio,
|
||||||
handleBackFromComposio,
|
handleBackFromComposio,
|
||||||
handleConnectComposioToolkit,
|
handleConnectComposioToolkit,
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@ import type { FC } from "react";
|
||||||
import { EnumConnectorName } from "@/contracts/enums/connector";
|
import { EnumConnectorName } from "@/contracts/enums/connector";
|
||||||
import type { SearchSourceConnector } from "@/contracts/types/connector.types";
|
import type { SearchSourceConnector } from "@/contracts/types/connector.types";
|
||||||
import { ConnectorCard } from "../components/connector-card";
|
import { ConnectorCard } from "../components/connector-card";
|
||||||
import { ComposioConnectorCard } from "../components/composio-connector-card";
|
|
||||||
import { CRAWLERS, OAUTH_CONNECTORS, OTHER_CONNECTORS, COMPOSIO_CONNECTORS } from "../constants/connector-constants";
|
import { CRAWLERS, OAUTH_CONNECTORS, OTHER_CONNECTORS, COMPOSIO_CONNECTORS } from "../constants/connector-constants";
|
||||||
import { getDocumentCountForConnector } from "../utils/connector-document-mapping";
|
import { getDocumentCountForConnector } from "../utils/connector-document-mapping";
|
||||||
|
|
||||||
|
|
@ -29,13 +28,12 @@ interface AllConnectorsTabProps {
|
||||||
allConnectors: SearchSourceConnector[] | undefined;
|
allConnectors: SearchSourceConnector[] | undefined;
|
||||||
documentTypeCounts?: Record<string, number>;
|
documentTypeCounts?: Record<string, number>;
|
||||||
indexingConnectorIds?: Set<number>;
|
indexingConnectorIds?: Set<number>;
|
||||||
onConnectOAuth: (connector: (typeof OAUTH_CONNECTORS)[number]) => void;
|
onConnectOAuth: (connector: (typeof OAUTH_CONNECTORS)[number] | (typeof COMPOSIO_CONNECTORS)[number]) => void;
|
||||||
onConnectNonOAuth?: (connectorType: string) => void;
|
onConnectNonOAuth?: (connectorType: string) => void;
|
||||||
onCreateWebcrawler?: () => void;
|
onCreateWebcrawler?: () => void;
|
||||||
onCreateYouTubeCrawler?: () => void;
|
onCreateYouTubeCrawler?: () => void;
|
||||||
onManage?: (connector: SearchSourceConnector) => void;
|
onManage?: (connector: SearchSourceConnector) => void;
|
||||||
onViewAccountsList?: (connectorType: string, connectorTitle: string) => void;
|
onViewAccountsList?: (connectorType: string, connectorTitle: string) => void;
|
||||||
onOpenComposio?: () => void;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const AllConnectorsTab: FC<AllConnectorsTabProps> = ({
|
export const AllConnectorsTab: FC<AllConnectorsTabProps> = ({
|
||||||
|
|
@ -51,7 +49,6 @@ export const AllConnectorsTab: FC<AllConnectorsTabProps> = ({
|
||||||
onCreateYouTubeCrawler,
|
onCreateYouTubeCrawler,
|
||||||
onManage,
|
onManage,
|
||||||
onViewAccountsList,
|
onViewAccountsList,
|
||||||
onOpenComposio,
|
|
||||||
}) => {
|
}) => {
|
||||||
// Filter connectors based on search
|
// Filter connectors based on search
|
||||||
const filteredOAuth = OAUTH_CONNECTORS.filter(
|
const filteredOAuth = OAUTH_CONNECTORS.filter(
|
||||||
|
|
@ -79,23 +76,16 @@ export const AllConnectorsTab: FC<AllConnectorsTabProps> = ({
|
||||||
c.description.toLowerCase().includes(searchQuery.toLowerCase())
|
c.description.toLowerCase().includes(searchQuery.toLowerCase())
|
||||||
);
|
);
|
||||||
|
|
||||||
// Count Composio connectors
|
|
||||||
const composioConnectorCount = allConnectors
|
|
||||||
? allConnectors.filter(
|
|
||||||
(c: SearchSourceConnector) => c.connector_type === EnumConnectorName.COMPOSIO_CONNECTOR
|
|
||||||
).length
|
|
||||||
: 0;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-8">
|
<div className="space-y-8">
|
||||||
{/* Quick Connect */}
|
{/* Managed OAuth (Composio Integrations) */}
|
||||||
{filteredOAuth.length > 0 && (
|
{filteredComposio.length > 0 && (
|
||||||
<section>
|
<section>
|
||||||
<div className="flex items-center gap-2 mb-4">
|
<div className="flex items-center gap-2 mb-4">
|
||||||
<h3 className="text-sm font-semibold text-muted-foreground">Quick Connect</h3>
|
<h3 className="text-sm font-semibold text-muted-foreground">Managed OAuth</h3>
|
||||||
</div>
|
</div>
|
||||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
|
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
|
||||||
{filteredOAuth.map((connector) => {
|
{filteredComposio.map((connector) => {
|
||||||
const isConnected = connectedTypes.has(connector.connectorType);
|
const isConnected = connectedTypes.has(connector.connectorType);
|
||||||
const isConnecting = connectingId === connector.id;
|
const isConnecting = connectingId === connector.id;
|
||||||
|
|
||||||
|
|
@ -109,17 +99,6 @@ export const AllConnectorsTab: FC<AllConnectorsTabProps> = ({
|
||||||
|
|
||||||
const accountCount = typeConnectors.length;
|
const accountCount = typeConnectors.length;
|
||||||
|
|
||||||
// Get the most recent last_indexed_at across all accounts
|
|
||||||
const mostRecentLastIndexed = typeConnectors.reduce<string | undefined>(
|
|
||||||
(latest, c) => {
|
|
||||||
if (!c.last_indexed_at) return latest;
|
|
||||||
if (!latest) return c.last_indexed_at;
|
|
||||||
return new Date(c.last_indexed_at) > new Date(latest)
|
|
||||||
? c.last_indexed_at
|
|
||||||
: latest;
|
|
||||||
},
|
|
||||||
undefined
|
|
||||||
);
|
|
||||||
|
|
||||||
const documentCount = getDocumentCountForConnector(
|
const documentCount = getDocumentCountForConnector(
|
||||||
connector.connectorType,
|
connector.connectorType,
|
||||||
|
|
@ -154,26 +133,57 @@ export const AllConnectorsTab: FC<AllConnectorsTabProps> = ({
|
||||||
</section>
|
</section>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Composio Integrations */}
|
{/* Quick Connect */}
|
||||||
{filteredComposio.length > 0 && onOpenComposio && (
|
{filteredOAuth.length > 0 && (
|
||||||
<section>
|
<section>
|
||||||
<div className="flex items-center gap-2 mb-4">
|
<div className="flex items-center gap-2 mb-4">
|
||||||
<h3 className="text-sm font-semibold text-muted-foreground">Managed OAuth</h3>
|
<h3 className="text-sm font-semibold text-muted-foreground">Quick Connect</h3>
|
||||||
<span className="text-[10px] px-1.5 py-0.5 rounded-full bg-violet-500/10 text-violet-600 dark:text-violet-400 border border-violet-500/20 font-medium">
|
|
||||||
No verification needed
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
|
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
|
||||||
{filteredComposio.map((connector) => (
|
{filteredOAuth.map((connector) => {
|
||||||
<ComposioConnectorCard
|
const isConnected = connectedTypes.has(connector.connectorType);
|
||||||
key={connector.id}
|
const isConnecting = connectingId === connector.id;
|
||||||
id={connector.id}
|
|
||||||
title={connector.title}
|
// Find all connectors of this type
|
||||||
description={connector.description}
|
const typeConnectors =
|
||||||
connectorCount={composioConnectorCount}
|
isConnected && allConnectors
|
||||||
onConnect={onOpenComposio}
|
? allConnectors.filter(
|
||||||
/>
|
(c: SearchSourceConnector) => c.connector_type === connector.connectorType
|
||||||
))}
|
)
|
||||||
|
: [];
|
||||||
|
|
||||||
|
const accountCount = typeConnectors.length;
|
||||||
|
|
||||||
|
|
||||||
|
const documentCount = getDocumentCountForConnector(
|
||||||
|
connector.connectorType,
|
||||||
|
documentTypeCounts
|
||||||
|
);
|
||||||
|
|
||||||
|
// Check if any account is currently indexing
|
||||||
|
const isIndexing = typeConnectors.some((c) => indexingConnectorIds?.has(c.id));
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ConnectorCard
|
||||||
|
key={connector.id}
|
||||||
|
id={connector.id}
|
||||||
|
title={connector.title}
|
||||||
|
description={connector.description}
|
||||||
|
connectorType={connector.connectorType}
|
||||||
|
isConnected={isConnected}
|
||||||
|
isConnecting={isConnecting}
|
||||||
|
documentCount={documentCount}
|
||||||
|
accountCount={accountCount}
|
||||||
|
isIndexing={isIndexing}
|
||||||
|
onConnect={() => onConnectOAuth(connector)}
|
||||||
|
onManage={
|
||||||
|
isConnected && onViewAccountsList
|
||||||
|
? () => onViewAccountsList(connector.connectorType, connector.title)
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,10 @@ export const CONNECTOR_TO_DOCUMENT_TYPE: Record<string, string> = {
|
||||||
// Special mappings (connector type differs from document type)
|
// Special mappings (connector type differs from document type)
|
||||||
GOOGLE_DRIVE_CONNECTOR: "GOOGLE_DRIVE_FILE",
|
GOOGLE_DRIVE_CONNECTOR: "GOOGLE_DRIVE_FILE",
|
||||||
WEBCRAWLER_CONNECTOR: "CRAWLED_URL",
|
WEBCRAWLER_CONNECTOR: "CRAWLED_URL",
|
||||||
COMPOSIO_CONNECTOR: "COMPOSIO_CONNECTOR",
|
// Composio connectors map to their own document types
|
||||||
|
COMPOSIO_GOOGLE_DRIVE_CONNECTOR: "COMPOSIO_GOOGLE_DRIVE_CONNECTOR",
|
||||||
|
COMPOSIO_GMAIL_CONNECTOR: "COMPOSIO_GMAIL_CONNECTOR",
|
||||||
|
COMPOSIO_GOOGLE_CALENDAR_CONNECTOR: "COMPOSIO_GOOGLE_CALENDAR_CONNECTOR",
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -24,5 +24,7 @@ export enum EnumConnectorName {
|
||||||
YOUTUBE_CONNECTOR = "YOUTUBE_CONNECTOR",
|
YOUTUBE_CONNECTOR = "YOUTUBE_CONNECTOR",
|
||||||
CIRCLEBACK_CONNECTOR = "CIRCLEBACK_CONNECTOR",
|
CIRCLEBACK_CONNECTOR = "CIRCLEBACK_CONNECTOR",
|
||||||
MCP_CONNECTOR = "MCP_CONNECTOR",
|
MCP_CONNECTOR = "MCP_CONNECTOR",
|
||||||
COMPOSIO_CONNECTOR = "COMPOSIO_CONNECTOR",
|
COMPOSIO_GOOGLE_DRIVE_CONNECTOR = "COMPOSIO_GOOGLE_DRIVE_CONNECTOR",
|
||||||
|
COMPOSIO_GMAIL_CONNECTOR = "COMPOSIO_GMAIL_CONNECTOR",
|
||||||
|
COMPOSIO_GOOGLE_CALENDAR_CONNECTOR = "COMPOSIO_GOOGLE_CALENDAR_CONNECTOR",
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -66,8 +66,12 @@ export const getConnectorIcon = (connectorType: EnumConnectorName | string, clas
|
||||||
return <IconUsersGroup {...iconProps} />;
|
return <IconUsersGroup {...iconProps} />;
|
||||||
case EnumConnectorName.MCP_CONNECTOR:
|
case EnumConnectorName.MCP_CONNECTOR:
|
||||||
return <Image src="/connectors/modelcontextprotocol.svg" alt="MCP" {...imgProps} />;
|
return <Image src="/connectors/modelcontextprotocol.svg" alt="MCP" {...imgProps} />;
|
||||||
case EnumConnectorName.COMPOSIO_CONNECTOR:
|
case EnumConnectorName.COMPOSIO_GOOGLE_DRIVE_CONNECTOR:
|
||||||
return <Image src="/connectors/composio.svg" alt="Composio" {...imgProps} />;
|
return <Image src="/connectors/google-drive.svg" alt="Google Drive" {...imgProps} />;
|
||||||
|
case EnumConnectorName.COMPOSIO_GMAIL_CONNECTOR:
|
||||||
|
return <Image src="/connectors/google-gmail.svg" alt="Gmail" {...imgProps} />;
|
||||||
|
case EnumConnectorName.COMPOSIO_GOOGLE_CALENDAR_CONNECTOR:
|
||||||
|
return <Image src="/connectors/google-calendar.svg" alt="Google Calendar" {...imgProps} />;
|
||||||
// Additional cases for non-enum connector types
|
// Additional cases for non-enum connector types
|
||||||
case "YOUTUBE_CONNECTOR":
|
case "YOUTUBE_CONNECTOR":
|
||||||
return <Image src="/connectors/youtube.svg" alt="YouTube" {...imgProps} />;
|
return <Image src="/connectors/youtube.svg" alt="YouTube" {...imgProps} />;
|
||||||
|
|
@ -87,8 +91,12 @@ export const getConnectorIcon = (connectorType: EnumConnectorName | string, clas
|
||||||
return <File {...iconProps} />;
|
return <File {...iconProps} />;
|
||||||
case "GOOGLE_DRIVE_FILE":
|
case "GOOGLE_DRIVE_FILE":
|
||||||
return <File {...iconProps} />;
|
return <File {...iconProps} />;
|
||||||
case "COMPOSIO_CONNECTOR":
|
case "COMPOSIO_GOOGLE_DRIVE_CONNECTOR":
|
||||||
return <Image src="/connectors/composio.svg" alt="Composio" {...imgProps} />;
|
return <Image src="/connectors/google-drive.svg" alt="Google Drive" {...imgProps} />;
|
||||||
|
case "COMPOSIO_GMAIL_CONNECTOR":
|
||||||
|
return <Image src="/connectors/google-gmail.svg" alt="Gmail" {...imgProps} />;
|
||||||
|
case "COMPOSIO_GOOGLE_CALENDAR_CONNECTOR":
|
||||||
|
return <Image src="/connectors/google-calendar.svg" alt="Google Calendar" {...imgProps} />;
|
||||||
case "NOTE":
|
case "NOTE":
|
||||||
return <FileText {...iconProps} />;
|
return <FileText {...iconProps} />;
|
||||||
case "EXTENSION":
|
case "EXTENSION":
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,9 @@ export const searchSourceConnectorTypeEnum = z.enum([
|
||||||
"BOOKSTACK_CONNECTOR",
|
"BOOKSTACK_CONNECTOR",
|
||||||
"CIRCLEBACK_CONNECTOR",
|
"CIRCLEBACK_CONNECTOR",
|
||||||
"MCP_CONNECTOR",
|
"MCP_CONNECTOR",
|
||||||
"COMPOSIO_CONNECTOR",
|
"COMPOSIO_GOOGLE_DRIVE_CONNECTOR",
|
||||||
|
"COMPOSIO_GMAIL_CONNECTOR",
|
||||||
|
"COMPOSIO_GOOGLE_CALENDAR_CONNECTOR",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
export const searchSourceConnector = z.object({
|
export const searchSourceConnector = z.object({
|
||||||
|
|
@ -149,6 +151,13 @@ export const googleDriveIndexBody = z.object({
|
||||||
name: z.string(),
|
name: z.string(),
|
||||||
})
|
})
|
||||||
),
|
),
|
||||||
|
indexing_options: z
|
||||||
|
.object({
|
||||||
|
max_files_per_folder: z.number().int().min(1).max(1000),
|
||||||
|
incremental_sync: z.boolean(),
|
||||||
|
include_subfolders: z.boolean(),
|
||||||
|
})
|
||||||
|
.optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,9 @@ export const documentTypeEnum = z.enum([
|
||||||
"CIRCLEBACK",
|
"CIRCLEBACK",
|
||||||
"SURFSENSE_DOCS",
|
"SURFSENSE_DOCS",
|
||||||
"NOTE",
|
"NOTE",
|
||||||
"COMPOSIO_CONNECTOR",
|
"COMPOSIO_GOOGLE_DRIVE_CONNECTOR",
|
||||||
|
"COMPOSIO_GMAIL_CONNECTOR",
|
||||||
|
"COMPOSIO_GOOGLE_CALENDAR_CONNECTOR",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
export const document = z.object({
|
export const document = z.object({
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue