mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-06-08 20:25:19 +02:00
feat: add Notion OAuth integration and connector routes
- Introduced Notion OAuth support with new environment variables for client ID, client secret, and redirect URI. - Implemented Notion connector routes for OAuth flow, including authorization and callback handling. - Updated existing components to accommodate Notion integration, including validation changes and connector configuration. - Enhanced the Notion indexer to utilize OAuth access tokens instead of integration tokens. - Adjusted UI components to reflect the new Notion connector without requiring special configuration.
This commit is contained in:
parent
2b01120c2b
commit
c5b184d475
14 changed files with 333 additions and 76 deletions
|
|
@ -38,7 +38,11 @@ GOOGLE_OAUTH_CLIENT_SECRET=GOCSV
|
|||
GOOGLE_CALENDAR_REDIRECT_URI=http://localhost:8000/api/v1/auth/google/calendar/connector/callback
|
||||
GOOGLE_GMAIL_REDIRECT_URI=http://localhost:8000/api/v1/auth/google/gmail/connector/callback
|
||||
GOOGLE_DRIVE_REDIRECT_URI=http://localhost:8000/api/v1/auth/google/drive/connector/callback
|
||||
GOOGLE_DRIVE_REDIRECT_URI=http://localhost:8000/api/v1/auth/google/drive/connector/callback
|
||||
|
||||
# Notion OAuth for Notion Connector
|
||||
NOTION_CLIENT_ID=your_notion_client_id
|
||||
NOTION_CLIENT_SECRET=your_notion_client_secret
|
||||
NOTION_REDIRECT_URI=http://localhost:8000/api/v1/auth/notion/connector/callback
|
||||
|
||||
# Airtable OAuth for Aitable Connector
|
||||
AIRTABLE_CLIENT_ID=your_airtable_client_id
|
||||
|
|
|
|||
|
|
@ -90,6 +90,11 @@ class Config:
|
|||
AIRTABLE_CLIENT_SECRET = os.getenv("AIRTABLE_CLIENT_SECRET")
|
||||
AIRTABLE_REDIRECT_URI = os.getenv("AIRTABLE_REDIRECT_URI")
|
||||
|
||||
# Notion OAuth
|
||||
NOTION_CLIENT_ID = os.getenv("NOTION_CLIENT_ID")
|
||||
NOTION_CLIENT_SECRET = os.getenv("NOTION_CLIENT_SECRET")
|
||||
NOTION_REDIRECT_URI = os.getenv("NOTION_REDIRECT_URI")
|
||||
|
||||
# LLM instances are now managed per-user through the LLMConfig system
|
||||
# Legacy environment variables removed in favor of user-specific configurations
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ class NotionHistoryConnector:
|
|||
Initialize the NotionPageFetcher with a token.
|
||||
|
||||
Args:
|
||||
token (str): Notion integration token
|
||||
token (str): Notion OAuth access token
|
||||
"""
|
||||
self.notion = AsyncClient(auth=token)
|
||||
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ from .google_gmail_add_connector_route import (
|
|||
from .logs_routes import router as logs_router
|
||||
from .luma_add_connector_route import router as luma_add_connector_router
|
||||
from .new_chat_routes import router as new_chat_router
|
||||
from .notion_add_connector_route import router as notion_add_connector_router
|
||||
from .new_llm_config_routes import router as new_llm_config_router
|
||||
from .notes_routes import router as notes_router
|
||||
from .podcasts_routes import router as podcasts_router
|
||||
|
|
@ -40,6 +41,7 @@ router.include_router(google_gmail_add_connector_router)
|
|||
router.include_router(google_drive_add_connector_router)
|
||||
router.include_router(airtable_add_connector_router)
|
||||
router.include_router(luma_add_connector_router)
|
||||
router.include_router(notion_add_connector_router)
|
||||
router.include_router(new_llm_config_router) # LLM configs with prompt configuration
|
||||
router.include_router(logs_router)
|
||||
router.include_router(circleback_webhook_router) # Circleback meeting webhooks
|
||||
|
|
|
|||
246
surfsense_backend/app/routes/notion_add_connector_route.py
Normal file
246
surfsense_backend/app/routes/notion_add_connector_route.py
Normal file
|
|
@ -0,0 +1,246 @@
|
|||
"""
|
||||
Notion Connector OAuth Routes.
|
||||
|
||||
Handles OAuth 2.0 authentication flow for Notion connector.
|
||||
"""
|
||||
|
||||
import base64
|
||||
import json
|
||||
import logging
|
||||
from uuid import UUID
|
||||
|
||||
import httpx
|
||||
from fastapi import APIRouter, Depends, HTTPException, Request
|
||||
from fastapi.responses import RedirectResponse
|
||||
from pydantic import ValidationError
|
||||
from sqlalchemy.exc import IntegrityError
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy.future import select
|
||||
|
||||
from app.config import config
|
||||
from app.db import (
|
||||
SearchSourceConnector,
|
||||
SearchSourceConnectorType,
|
||||
User,
|
||||
get_async_session,
|
||||
)
|
||||
from app.users import current_active_user
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
# Notion OAuth endpoints
|
||||
AUTHORIZATION_URL = "https://api.notion.com/v1/oauth/authorize"
|
||||
TOKEN_URL = "https://api.notion.com/v1/oauth/token"
|
||||
|
||||
|
||||
def make_basic_auth_header(client_id: str, client_secret: str) -> str:
|
||||
"""Create Basic Auth header for Notion OAuth."""
|
||||
credentials = f"{client_id}:{client_secret}".encode()
|
||||
b64 = base64.b64encode(credentials).decode("ascii")
|
||||
return f"Basic {b64}"
|
||||
|
||||
|
||||
@router.get("/auth/notion/connector/add")
|
||||
async def connect_notion(space_id: int, user: User = Depends(current_active_user)):
|
||||
"""
|
||||
Initiate Notion OAuth flow.
|
||||
|
||||
Args:
|
||||
space_id: The search space ID
|
||||
user: Current authenticated user
|
||||
|
||||
Returns:
|
||||
Authorization URL for redirect
|
||||
"""
|
||||
try:
|
||||
if not space_id:
|
||||
raise HTTPException(status_code=400, detail="space_id is required")
|
||||
|
||||
if not config.NOTION_CLIENT_ID:
|
||||
raise HTTPException(
|
||||
status_code=500, detail="Notion OAuth not configured."
|
||||
)
|
||||
|
||||
# Generate state parameter
|
||||
state_payload = json.dumps(
|
||||
{
|
||||
"space_id": space_id,
|
||||
"user_id": str(user.id),
|
||||
}
|
||||
)
|
||||
state_encoded = base64.urlsafe_b64encode(state_payload.encode()).decode()
|
||||
|
||||
# Build authorization URL
|
||||
from urllib.parse import urlencode
|
||||
|
||||
auth_params = {
|
||||
"client_id": config.NOTION_CLIENT_ID,
|
||||
"response_type": "code",
|
||||
"owner": "user", # Allows both admins and members to authorize
|
||||
"redirect_uri": config.NOTION_REDIRECT_URI,
|
||||
"state": state_encoded,
|
||||
}
|
||||
|
||||
auth_url = f"{AUTHORIZATION_URL}?{urlencode(auth_params)}"
|
||||
|
||||
logger.info(
|
||||
f"Generated Notion OAuth URL for user {user.id}, space {space_id}"
|
||||
)
|
||||
return {"auth_url": auth_url}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to initiate Notion OAuth: {e!s}", exc_info=True)
|
||||
raise HTTPException(
|
||||
status_code=500, detail=f"Failed to initiate Notion OAuth: {e!s}"
|
||||
) from e
|
||||
|
||||
|
||||
@router.get("/auth/notion/connector/callback")
|
||||
async def notion_callback(
|
||||
request: Request,
|
||||
code: str,
|
||||
state: str,
|
||||
session: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
"""
|
||||
Handle Notion OAuth callback.
|
||||
|
||||
Args:
|
||||
request: FastAPI request object
|
||||
code: Authorization code from Notion
|
||||
state: State parameter containing user/space info
|
||||
session: Database session
|
||||
|
||||
Returns:
|
||||
Redirect response to frontend
|
||||
"""
|
||||
try:
|
||||
# Decode and parse the state
|
||||
try:
|
||||
decoded_state = base64.urlsafe_b64decode(state.encode()).decode()
|
||||
data = json.loads(decoded_state)
|
||||
except Exception as e:
|
||||
raise HTTPException(
|
||||
status_code=400, detail=f"Invalid state parameter: {e!s}"
|
||||
) from e
|
||||
|
||||
user_id = UUID(data["user_id"])
|
||||
space_id = data["space_id"]
|
||||
|
||||
# Exchange authorization code for access token
|
||||
auth_header = make_basic_auth_header(
|
||||
config.NOTION_CLIENT_ID, config.NOTION_CLIENT_SECRET
|
||||
)
|
||||
|
||||
token_data = {
|
||||
"grant_type": "authorization_code",
|
||||
"code": code,
|
||||
"redirect_uri": config.NOTION_REDIRECT_URI,
|
||||
}
|
||||
|
||||
async with httpx.AsyncClient() as client:
|
||||
token_response = await client.post(
|
||||
TOKEN_URL,
|
||||
json=token_data,
|
||||
headers={
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": auth_header,
|
||||
},
|
||||
timeout=30.0,
|
||||
)
|
||||
|
||||
if token_response.status_code != 200:
|
||||
error_detail = token_response.text
|
||||
try:
|
||||
error_json = token_response.json()
|
||||
error_detail = error_json.get("error_description", error_detail)
|
||||
except Exception:
|
||||
pass
|
||||
raise HTTPException(
|
||||
status_code=400, detail=f"Token exchange failed: {error_detail}"
|
||||
)
|
||||
|
||||
token_json = token_response.json()
|
||||
|
||||
# Notion returns access_token and workspace information
|
||||
# Store the access token and workspace info in connector config
|
||||
connector_config = {
|
||||
"access_token": token_json["access_token"],
|
||||
"workspace_id": token_json.get("workspace_id"),
|
||||
"workspace_name": token_json.get("workspace_name"),
|
||||
"workspace_icon": token_json.get("workspace_icon"),
|
||||
"bot_id": token_json.get("bot_id"),
|
||||
}
|
||||
|
||||
# Check if connector already exists for this search space and user
|
||||
existing_connector_result = await session.execute(
|
||||
select(SearchSourceConnector).filter(
|
||||
SearchSourceConnector.search_space_id == space_id,
|
||||
SearchSourceConnector.user_id == user_id,
|
||||
SearchSourceConnector.connector_type
|
||||
== SearchSourceConnectorType.NOTION_CONNECTOR,
|
||||
)
|
||||
)
|
||||
existing_connector = existing_connector_result.scalars().first()
|
||||
|
||||
if existing_connector:
|
||||
# Update existing connector
|
||||
existing_connector.config = connector_config
|
||||
existing_connector.name = "Notion Connector"
|
||||
existing_connector.is_indexable = True
|
||||
logger.info(
|
||||
f"Updated existing Notion connector for user {user_id} in space {space_id}"
|
||||
)
|
||||
else:
|
||||
# Create new connector
|
||||
new_connector = SearchSourceConnector(
|
||||
name="Notion Connector",
|
||||
connector_type=SearchSourceConnectorType.NOTION_CONNECTOR,
|
||||
is_indexable=True,
|
||||
config=connector_config,
|
||||
search_space_id=space_id,
|
||||
user_id=user_id,
|
||||
)
|
||||
session.add(new_connector)
|
||||
logger.info(
|
||||
f"Created new Notion connector for user {user_id} in space {space_id}"
|
||||
)
|
||||
|
||||
try:
|
||||
await session.commit()
|
||||
logger.info(f"Successfully saved Notion connector for user {user_id}")
|
||||
|
||||
# Redirect to the frontend with success params
|
||||
return RedirectResponse(
|
||||
url=f"{config.NEXT_FRONTEND_URL}/dashboard/{space_id}/new-chat?modal=connectors&tab=all&success=true&connector=notion-connector"
|
||||
)
|
||||
|
||||
except ValidationError as e:
|
||||
await session.rollback()
|
||||
raise HTTPException(
|
||||
status_code=422, detail=f"Validation error: {e!s}"
|
||||
) from e
|
||||
except IntegrityError as e:
|
||||
await session.rollback()
|
||||
raise HTTPException(
|
||||
status_code=409,
|
||||
detail=f"Integrity error: A connector with this type already exists. {e!s}",
|
||||
) from e
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to create search source connector: {e!s}")
|
||||
await session.rollback()
|
||||
raise HTTPException(
|
||||
status_code=500,
|
||||
detail=f"Failed to create search source connector: {e!s}",
|
||||
) from e
|
||||
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to complete Notion OAuth: {e!s}", exc_info=True)
|
||||
raise HTTPException(
|
||||
status_code=500, detail=f"Failed to complete Notion OAuth: {e!s}"
|
||||
) from e
|
||||
|
||||
|
|
@ -2,7 +2,7 @@
|
|||
Notion connector indexer.
|
||||
"""
|
||||
|
||||
from datetime import datetime, timedelta
|
||||
from datetime import datetime
|
||||
|
||||
from sqlalchemy.exc import SQLAlchemyError
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
|
@ -20,6 +20,7 @@ from app.utils.document_converters import (
|
|||
|
||||
from .base import (
|
||||
build_document_metadata_string,
|
||||
calculate_date_range,
|
||||
check_document_by_unique_identifier,
|
||||
get_connector_by_id,
|
||||
get_current_timestamp,
|
||||
|
|
@ -91,16 +92,16 @@ async def index_notion_pages(
|
|||
f"Connector with ID {connector_id} not found or is not a Notion connector",
|
||||
)
|
||||
|
||||
# Get the Notion token from the connector config
|
||||
notion_token = connector.config.get("NOTION_INTEGRATION_TOKEN")
|
||||
# Get the Notion access token from the connector config (OAuth-based)
|
||||
notion_token = connector.config.get("access_token")
|
||||
if not notion_token:
|
||||
await task_logger.log_task_failure(
|
||||
log_entry,
|
||||
f"Notion integration token not found in connector config for connector {connector_id}",
|
||||
"Missing Notion token",
|
||||
f"Notion access token not found in connector config for connector {connector_id}",
|
||||
"Missing Notion access token",
|
||||
{"error_type": "MissingToken"},
|
||||
)
|
||||
return 0, "Notion integration token not found in connector config"
|
||||
return 0, "Notion access token not found in connector config"
|
||||
|
||||
# Initialize Notion client
|
||||
await task_logger.log_task_progress(
|
||||
|
|
@ -111,38 +112,24 @@ async def index_notion_pages(
|
|||
|
||||
logger.info(f"Initializing Notion client for connector {connector_id}")
|
||||
|
||||
# Calculate date range
|
||||
if start_date is None or end_date is None:
|
||||
# Fall back to calculating dates
|
||||
calculated_end_date = datetime.now()
|
||||
calculated_start_date = calculated_end_date - timedelta(
|
||||
days=365
|
||||
) # Check for last 1 year of pages
|
||||
# Handle 'undefined' string from frontend (treat as None)
|
||||
if start_date == "undefined" or start_date == "":
|
||||
start_date = None
|
||||
if end_date == "undefined" or end_date == "":
|
||||
end_date = None
|
||||
|
||||
# Use calculated dates if not provided
|
||||
if start_date is None:
|
||||
start_date_iso = calculated_start_date.strftime("%Y-%m-%dT%H:%M:%SZ")
|
||||
else:
|
||||
# Convert YYYY-MM-DD to ISO format
|
||||
start_date_iso = datetime.strptime(start_date, "%Y-%m-%d").strftime(
|
||||
"%Y-%m-%dT%H:%M:%SZ"
|
||||
)
|
||||
# Calculate date range using the shared utility function
|
||||
start_date_str, end_date_str = calculate_date_range(
|
||||
connector, start_date, end_date, default_days_back=365
|
||||
)
|
||||
|
||||
if end_date is None:
|
||||
end_date_iso = calculated_end_date.strftime("%Y-%m-%dT%H:%M:%SZ")
|
||||
else:
|
||||
# Convert YYYY-MM-DD to ISO format
|
||||
end_date_iso = datetime.strptime(end_date, "%Y-%m-%d").strftime(
|
||||
"%Y-%m-%dT%H:%M:%SZ"
|
||||
)
|
||||
else:
|
||||
# Convert provided dates to ISO format for Notion API
|
||||
start_date_iso = datetime.strptime(start_date, "%Y-%m-%d").strftime(
|
||||
"%Y-%m-%dT%H:%M:%SZ"
|
||||
)
|
||||
end_date_iso = datetime.strptime(end_date, "%Y-%m-%d").strftime(
|
||||
"%Y-%m-%dT%H:%M:%SZ"
|
||||
)
|
||||
# Convert YYYY-MM-DD to ISO format for Notion API
|
||||
start_date_iso = datetime.strptime(start_date_str, "%Y-%m-%d").strftime(
|
||||
"%Y-%m-%dT%H:%M:%SZ"
|
||||
)
|
||||
end_date_iso = datetime.strptime(end_date_str, "%Y-%m-%d").strftime(
|
||||
"%Y-%m-%dT%H:%M:%SZ"
|
||||
)
|
||||
|
||||
notion_client = NotionHistoryConnector(token=notion_token)
|
||||
|
||||
|
|
|
|||
|
|
@ -515,7 +515,13 @@ def validate_connector_config(
|
|||
},
|
||||
"SLACK_CONNECTOR": {"required": ["SLACK_BOT_TOKEN"], "validators": {}},
|
||||
"NOTION_CONNECTOR": {
|
||||
"required": ["NOTION_INTEGRATION_TOKEN"],
|
||||
"required": ["access_token"], # OAuth-based only
|
||||
"optional": [
|
||||
"workspace_id", # OAuth fields
|
||||
"workspace_name",
|
||||
"workspace_icon",
|
||||
"bot_id",
|
||||
],
|
||||
"validators": {},
|
||||
},
|
||||
"GITHUB_CONNECTOR": {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
"use client";
|
||||
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { Info, Webhook } from "lucide-react";
|
||||
import { Webhook } from "lucide-react";
|
||||
import type { FC } from "react";
|
||||
import { useRef } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@ import { JiraConnectForm } from "./components/jira-connect-form";
|
|||
import { LinearConnectForm } from "./components/linear-connect-form";
|
||||
import { LinkupApiConnectForm } from "./components/linkup-api-connect-form";
|
||||
import { LumaConnectForm } from "./components/luma-connect-form";
|
||||
import { NotionConnectForm } from "./components/notion-connect-form";
|
||||
import { SearxngConnectForm } from "./components/searxng-connect-form";
|
||||
import { SlackConnectForm } from "./components/slack-connect-form";
|
||||
import { TavilyApiConnectForm } from "./components/tavily-api-connect-form";
|
||||
|
|
@ -59,8 +58,6 @@ export function getConnectFormComponent(connectorType: string): ConnectFormCompo
|
|||
return SlackConnectForm;
|
||||
case "DISCORD_CONNECTOR":
|
||||
return DiscordConnectForm;
|
||||
case "NOTION_CONNECTOR":
|
||||
return NotionConnectForm;
|
||||
case "CONFLUENCE_CONNECTOR":
|
||||
return ConfluenceConnectForm;
|
||||
case "BOOKSTACK_CONNECTOR":
|
||||
|
|
|
|||
|
|
@ -15,7 +15,6 @@ import { JiraConfig } from "./components/jira-config";
|
|||
import { LinearConfig } from "./components/linear-config";
|
||||
import { LinkupApiConfig } from "./components/linkup-api-config";
|
||||
import { LumaConfig } from "./components/luma-config";
|
||||
import { NotionConfig } from "./components/notion-config";
|
||||
import { SearxngConfig } from "./components/searxng-config";
|
||||
import { SlackConfig } from "./components/slack-config";
|
||||
import { TavilyApiConfig } from "./components/tavily-api-config";
|
||||
|
|
@ -56,8 +55,6 @@ export function getConnectorConfigComponent(
|
|||
return SlackConfig;
|
||||
case "DISCORD_CONNECTOR":
|
||||
return DiscordConfig;
|
||||
case "NOTION_CONNECTOR":
|
||||
return NotionConfig;
|
||||
case "CONFLUENCE_CONNECTOR":
|
||||
return ConfluenceConfig;
|
||||
case "BOOKSTACK_CONNECTOR":
|
||||
|
|
@ -72,7 +69,7 @@ export function getConnectorConfigComponent(
|
|||
return LumaConfig;
|
||||
case "CIRCLEBACK_CONNECTOR":
|
||||
return CirclebackConfig;
|
||||
// OAuth connectors (Gmail, Calendar, Airtable) and others don't need special config UI
|
||||
// OAuth connectors (Gmail, Calendar, Airtable, Notion) and others don't need special config UI
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -55,7 +55,6 @@ export const ConnectorConnectView: FC<ConnectorConnectViewProps> = ({
|
|||
ELASTICSEARCH_CONNECTOR: "elasticsearch-connect-form",
|
||||
SLACK_CONNECTOR: "slack-connect-form",
|
||||
DISCORD_CONNECTOR: "discord-connect-form",
|
||||
NOTION_CONNECTOR: "notion-connect-form",
|
||||
CONFLUENCE_CONNECTOR: "confluence-connect-form",
|
||||
BOOKSTACK_CONNECTOR: "bookstack-connect-form",
|
||||
GITHUB_CONNECTOR: "github-connect-form",
|
||||
|
|
|
|||
|
|
@ -59,6 +59,7 @@ export const ConnectorEditView: FC<ConnectorEditViewProps> = ({
|
|||
const [isScrolled, setIsScrolled] = useState(false);
|
||||
const [hasMoreContent, setHasMoreContent] = useState(false);
|
||||
const [showDisconnectConfirm, setShowDisconnectConfirm] = useState(false);
|
||||
const [isQuickIndexing, setIsQuickIndexing] = useState(false);
|
||||
const scrollContainerRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const checkScrollState = useCallback(() => {
|
||||
|
|
@ -94,6 +95,13 @@ export const ConnectorEditView: FC<ConnectorEditViewProps> = ({
|
|||
};
|
||||
}, [checkScrollState]);
|
||||
|
||||
// Reset local quick indexing state when indexing completes
|
||||
useEffect(() => {
|
||||
if (!isIndexing) {
|
||||
setIsQuickIndexing(false);
|
||||
}
|
||||
}, [isIndexing]);
|
||||
|
||||
const handleDisconnectClick = () => {
|
||||
setShowDisconnectConfirm(true);
|
||||
};
|
||||
|
|
@ -107,6 +115,13 @@ export const ConnectorEditView: FC<ConnectorEditViewProps> = ({
|
|||
setShowDisconnectConfirm(false);
|
||||
};
|
||||
|
||||
const handleQuickIndex = useCallback(() => {
|
||||
if (onQuickIndex) {
|
||||
setIsQuickIndexing(true);
|
||||
onQuickIndex();
|
||||
}
|
||||
}, [onQuickIndex]);
|
||||
|
||||
return (
|
||||
<div className="flex-1 flex flex-col min-h-0 overflow-hidden">
|
||||
{/* Fixed Header */}
|
||||
|
|
@ -146,11 +161,11 @@ export const ConnectorEditView: FC<ConnectorEditViewProps> = ({
|
|||
<Button
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
onClick={onQuickIndex}
|
||||
disabled={isIndexing || isSaving || isDisconnecting}
|
||||
onClick={handleQuickIndex}
|
||||
disabled={isQuickIndexing || isIndexing || isSaving || isDisconnecting}
|
||||
className="text-xs sm:text-sm bg-slate-400/10 dark:bg-white/10 hover:bg-slate-400/20 dark:hover:bg-white/20 border-slate-400/20 dark:border-white/20 w-full sm:w-auto"
|
||||
>
|
||||
{isIndexing ? (
|
||||
{isQuickIndexing || isIndexing ? (
|
||||
<>
|
||||
<RefreshCw className="mr-2 h-4 w-4 animate-spin" />
|
||||
Indexing...
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
import { ArrowLeft, Check, Info, Loader2 } from "lucide-react";
|
||||
import { type FC, useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||
import { useSearchParams } from "next/navigation";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import type { SearchSourceConnector } from "@/contracts/types/connector.types";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
|
@ -43,6 +44,9 @@ export const IndexingConfigurationView: FC<IndexingConfigurationViewProps> = ({
|
|||
onStartIndexing,
|
||||
onSkip,
|
||||
}) => {
|
||||
const searchParams = useSearchParams();
|
||||
const isFromOAuth = searchParams.get("view") === "configure";
|
||||
|
||||
// Get connector-specific config component
|
||||
const ConnectorConfigComponent = useMemo(
|
||||
() => (connector ? getConnectorConfigComponent(connector.connector_type) : null),
|
||||
|
|
@ -94,15 +98,17 @@ export const IndexingConfigurationView: FC<IndexingConfigurationViewProps> = ({
|
|||
isScrolled && "shadow-sm"
|
||||
)}
|
||||
>
|
||||
{/* Back button */}
|
||||
<button
|
||||
type="button"
|
||||
onClick={onSkip}
|
||||
className="flex items-center gap-2 text-xs sm:text-sm text-muted-foreground hover:text-foreground mb-6 w-fit"
|
||||
>
|
||||
<ArrowLeft className="size-4" />
|
||||
Back to connectors
|
||||
</button>
|
||||
{/* Back button - only show if not from OAuth */}
|
||||
{!isFromOAuth && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={onSkip}
|
||||
className="flex items-center gap-2 text-xs sm:text-sm text-muted-foreground hover:text-foreground mb-6 w-fit"
|
||||
>
|
||||
<ArrowLeft className="size-4" />
|
||||
Back to connectors
|
||||
</button>
|
||||
)}
|
||||
|
||||
{/* Success header */}
|
||||
<div className="flex items-center gap-4 mb-6">
|
||||
|
|
@ -187,15 +193,7 @@ export const IndexingConfigurationView: FC<IndexingConfigurationViewProps> = ({
|
|||
</div>
|
||||
|
||||
{/* Fixed Footer - Action buttons */}
|
||||
<div className="flex-shrink-0 flex items-center justify-between px-6 sm:px-12 py-6 bg-muted">
|
||||
<Button
|
||||
variant="ghost"
|
||||
onClick={onSkip}
|
||||
disabled={isStartingIndexing}
|
||||
className="text-xs sm:text-sm"
|
||||
>
|
||||
Skip for now
|
||||
</Button>
|
||||
<div className="flex-shrink-0 flex items-center justify-end px-6 sm:px-12 py-6 bg-muted">
|
||||
<Button
|
||||
onClick={onStartIndexing}
|
||||
disabled={isStartingIndexing}
|
||||
|
|
|
|||
|
|
@ -30,6 +30,13 @@ export const OAUTH_CONNECTORS = [
|
|||
connectorType: EnumConnectorName.AIRTABLE_CONNECTOR,
|
||||
authEndpoint: "/api/v1/auth/airtable/connector/add/",
|
||||
},
|
||||
{
|
||||
id: "notion-connector",
|
||||
title: "Notion",
|
||||
description: "Search your Notion pages",
|
||||
connectorType: EnumConnectorName.NOTION_CONNECTOR,
|
||||
authEndpoint: "/api/v1/auth/notion/connector/add/",
|
||||
},
|
||||
] as const;
|
||||
|
||||
// Content Sources (tools that extract and import content from external sources)
|
||||
|
|
@ -62,12 +69,6 @@ export const OTHER_CONNECTORS = [
|
|||
description: "Search Discord messages",
|
||||
connectorType: EnumConnectorName.DISCORD_CONNECTOR,
|
||||
},
|
||||
{
|
||||
id: "notion-connector",
|
||||
title: "Notion",
|
||||
description: "Search Notion pages",
|
||||
connectorType: EnumConnectorName.NOTION_CONNECTOR,
|
||||
},
|
||||
{
|
||||
id: "confluence-connector",
|
||||
title: "Confluence",
|
||||
|
|
@ -143,7 +144,7 @@ export const OTHER_CONNECTORS = [
|
|||
{
|
||||
id: "circleback-connector",
|
||||
title: "Circleback",
|
||||
description: "Receive meeting notes via webhook",
|
||||
description: "Receive meeting notes, transcripts",
|
||||
connectorType: EnumConnectorName.CIRCLEBACK_CONNECTOR,
|
||||
},
|
||||
] as const;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue