chore: ran both frontend and backend linting

This commit is contained in:
Anish Sarkar 2026-01-03 00:18:17 +05:30
parent 45489423d1
commit 645e849d93
21 changed files with 148 additions and 155 deletions

View file

@ -109,14 +109,14 @@ class GoogleCalendarConnector:
raise RuntimeError(
"GOOGLE_CALENDAR_CONNECTOR connector not found; cannot persist refreshed token."
)
# Encrypt sensitive credentials before storing
from app.config import config
from app.utils.oauth_security import TokenEncryption
creds_dict = json.loads(self._credentials.to_json())
token_encrypted = connector.config.get("_token_encrypted", False)
if token_encrypted and config.SECRET_KEY:
token_encryption = TokenEncryption(config.SECRET_KEY)
# Encrypt sensitive fields
@ -125,15 +125,19 @@ class GoogleCalendarConnector:
creds_dict["token"]
)
if creds_dict.get("refresh_token"):
creds_dict["refresh_token"] = token_encryption.encrypt_token(
creds_dict["refresh_token"]
creds_dict["refresh_token"] = (
token_encryption.encrypt_token(
creds_dict["refresh_token"]
)
)
if creds_dict.get("client_secret"):
creds_dict["client_secret"] = token_encryption.encrypt_token(
creds_dict["client_secret"]
creds_dict["client_secret"] = (
token_encryption.encrypt_token(
creds_dict["client_secret"]
)
)
creds_dict["_token_encrypted"] = True
connector.config = creds_dict
flag_modified(connector, "config")
await self._session.commit()
@ -209,9 +213,15 @@ class GoogleCalendarConnector:
try:
# Validate date strings
if not start_date or start_date.lower() in ("undefined", "null", "none"):
return [], "Invalid start_date: must be a valid date string in YYYY-MM-DD format"
return (
[],
"Invalid start_date: must be a valid date string in YYYY-MM-DD format",
)
if not end_date or end_date.lower() in ("undefined", "null", "none"):
return [], "Invalid end_date: must be a valid date string in YYYY-MM-DD format"
return (
[],
"Invalid end_date: must be a valid date string in YYYY-MM-DD format",
)
service = await self._get_service()

View file

@ -43,14 +43,16 @@ async def get_valid_credentials(
if not connector:
raise ValueError(f"Connector {connector_id} not found")
config_data = connector.config.copy() # Work with a copy to avoid modifying original
config_data = (
connector.config.copy()
) # Work with a copy to avoid modifying original
# Decrypt credentials if they are encrypted
token_encrypted = config_data.get("_token_encrypted", False)
if token_encrypted and config.SECRET_KEY:
try:
token_encryption = TokenEncryption(config.SECRET_KEY)
# Decrypt sensitive fields
if config_data.get("token"):
config_data["token"] = token_encryption.decrypt_token(
@ -64,7 +66,7 @@ async def get_valid_credentials(
config_data["client_secret"] = token_encryption.decrypt_token(
config_data["client_secret"]
)
logger.info(
f"Decrypted Google Drive credentials for connector {connector_id}"
)
@ -104,10 +106,10 @@ async def get_valid_credentials(
credentials.refresh(Request())
creds_dict = json.loads(credentials.to_json())
# Encrypt sensitive credentials before storing
token_encrypted = connector.config.get("_token_encrypted", False)
if token_encrypted and config.SECRET_KEY:
token_encryption = TokenEncryption(config.SECRET_KEY)
# Encrypt sensitive fields
@ -124,7 +126,7 @@ async def get_valid_credentials(
creds_dict["client_secret"]
)
creds_dict["_token_encrypted"] = True
connector.config = creds_dict
flag_modified(connector, "config")
await session.commit()

View file

@ -44,9 +44,14 @@ class LinearConnector:
ValueError: If no Linear access token has been set
"""
if not self.access_token:
raise ValueError("Linear access token not initialized. Call set_token() first.")
raise ValueError(
"Linear access token not initialized. Call set_token() first."
)
return {"Content-Type": "application/json", "Authorization": f"Bearer {self.access_token}"}
return {
"Content-Type": "application/json",
"Authorization": f"Bearer {self.access_token}",
}
def execute_graphql_query(
self, query: str, variables: dict[str, Any] | None = None
@ -66,7 +71,9 @@ class LinearConnector:
Exception: If the API request fails
"""
if not self.access_token:
raise ValueError("Linear access token not initialized. Call set_token() first.")
raise ValueError(
"Linear access token not initialized. Call set_token() first."
)
headers = self.get_headers()
payload = {"query": query}
@ -174,9 +181,15 @@ class LinearConnector:
"""
# Validate date strings
if not start_date or start_date.lower() in ("undefined", "null", "none"):
return [], "Invalid start_date: must be a valid date string in YYYY-MM-DD format"
return (
[],
"Invalid start_date: must be a valid date string in YYYY-MM-DD format",
)
if not end_date or end_date.lower() in ("undefined", "null", "none"):
return [], "Invalid end_date: must be a valid date string in YYYY-MM-DD format"
return (
[],
"Invalid end_date: must be a valid date string in YYYY-MM-DD format",
)
# Convert date strings to ISO format
try:

View file

@ -15,13 +15,13 @@ from .google_drive_add_connector_route import (
from .google_gmail_add_connector_route import (
router as google_gmail_add_connector_router,
)
from .linear_add_connector_route import router as linear_add_connector_router
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 .linear_add_connector_route import router as linear_add_connector_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 .notion_add_connector_route import router as notion_add_connector_router
from .podcasts_routes import router as podcasts_router
from .rbac_routes import router as rbac_router
from .search_source_connectors_routes import router as search_source_connectors_router

View file

@ -191,7 +191,7 @@ async def airtable_callback(
except Exception:
# If state is invalid, we'll redirect without space_id
logger.warning("Failed to validate state in error handler")
# Redirect to frontend with error parameter
if space_id:
return RedirectResponse(
@ -201,16 +201,12 @@ async def airtable_callback(
return RedirectResponse(
url=f"{config.NEXT_FRONTEND_URL}/dashboard?error=airtable_oauth_denied"
)
# Validate required parameters for successful flow
if not code:
raise HTTPException(
status_code=400, detail="Missing authorization code"
)
raise HTTPException(status_code=400, detail="Missing authorization code")
if not state:
raise HTTPException(
status_code=400, detail="Missing state parameter"
)
raise HTTPException(status_code=400, detail="Missing state parameter")
# Validate and decode state with signature verification
state_manager = get_state_manager()
@ -226,7 +222,7 @@ async def airtable_callback(
user_id = UUID(data["user_id"])
space_id = data["space_id"]
code_verifier = data.get("code_verifier")
if not code_verifier:
raise HTTPException(
status_code=400, detail="Missing code_verifier in state parameter"
@ -273,7 +269,7 @@ async def airtable_callback(
token_encryption = get_token_encryption()
access_token = token_json.get("access_token")
refresh_token = token_json.get("refresh_token")
if not access_token:
raise HTTPException(
status_code=400, detail="No access token received from Airtable"
@ -296,7 +292,7 @@ async def airtable_callback(
expires_at=expires_at,
scope=token_json.get("scope"),
)
# Mark that tokens are encrypted for backward compatibility
credentials_dict = credentials.to_dict()
credentials_dict["_token_encrypted"] = True
@ -390,11 +386,11 @@ async def refresh_airtable_token(
logger.info(f"Refreshing Airtable token for connector {connector.id}")
credentials = AirtableAuthCredentialsBase.from_dict(connector.config)
# Decrypt tokens if they are encrypted
token_encryption = get_token_encryption()
is_encrypted = connector.config.get("_token_encrypted", False)
refresh_token = credentials.refresh_token
if is_encrypted and refresh_token:
try:
@ -404,7 +400,7 @@ async def refresh_airtable_token(
raise HTTPException(
status_code=500, detail="Failed to decrypt stored refresh token"
) from e
auth_header = make_basic_auth_header(
config.AIRTABLE_CLIENT_ID, config.AIRTABLE_CLIENT_SECRET
)
@ -444,7 +440,7 @@ async def refresh_airtable_token(
# Encrypt new tokens before storing
access_token = token_json.get("access_token")
new_refresh_token = token_json.get("refresh_token")
if not access_token:
raise HTTPException(
status_code=400, detail="No access token received from Airtable refresh"
@ -453,7 +449,9 @@ async def refresh_airtable_token(
# Update credentials object with encrypted tokens
credentials.access_token = token_encryption.encrypt_token(access_token)
if new_refresh_token:
credentials.refresh_token = token_encryption.encrypt_token(new_refresh_token)
credentials.refresh_token = token_encryption.encrypt_token(
new_refresh_token
)
credentials.expires_in = token_json.get("expires_in")
credentials.expires_at = expires_at
credentials.scope = token_json.get("scope")

View file

@ -142,13 +142,9 @@ async def calendar_callback(
# Validate required parameters for successful flow
if not code:
raise HTTPException(
status_code=400, detail="Missing authorization code"
)
raise HTTPException(status_code=400, detail="Missing authorization code")
if not state:
raise HTTPException(
status_code=400, detail="Missing state parameter"
)
raise HTTPException(status_code=400, detail="Missing state parameter")
# Validate and decode state with signature verification
state_manager = get_state_manager()
@ -178,7 +174,7 @@ async def calendar_callback(
# Encrypt sensitive credentials before storing
token_encryption = get_token_encryption()
# Encrypt sensitive fields: token, refresh_token, client_secret
if creds_dict.get("token"):
creds_dict["token"] = token_encryption.encrypt_token(creds_dict["token"])
@ -190,7 +186,7 @@ async def calendar_callback(
creds_dict["client_secret"] = token_encryption.encrypt_token(
creds_dict["client_secret"]
)
# Mark that credentials are encrypted for backward compatibility
creds_dict["_token_encrypted"] = True

View file

@ -68,6 +68,7 @@ def get_token_encryption() -> TokenEncryption:
_token_encryption = TokenEncryption(config.SECRET_KEY)
return _token_encryption
# Google Drive OAuth scopes
SCOPES = [
"https://www.googleapis.com/auth/drive.readonly", # Read-only access to Drive
@ -191,13 +192,9 @@ async def drive_callback(
# Validate required parameters for successful flow
if not code:
raise HTTPException(
status_code=400, detail="Missing authorization code"
)
raise HTTPException(status_code=400, detail="Missing authorization code")
if not state:
raise HTTPException(
status_code=400, detail="Missing state parameter"
)
raise HTTPException(status_code=400, detail="Missing state parameter")
# Validate and decode state with signature verification
state_manager = get_state_manager()
@ -232,7 +229,7 @@ async def drive_callback(
# Encrypt sensitive credentials before storing
token_encryption = get_token_encryption()
# Encrypt sensitive fields: token, refresh_token, client_secret
if creds_dict.get("token"):
creds_dict["token"] = token_encryption.encrypt_token(creds_dict["token"])
@ -244,7 +241,7 @@ async def drive_callback(
creds_dict["client_secret"] = token_encryption.encrypt_token(
creds_dict["client_secret"]
)
# Mark that credentials are encrypted for backward compatibility
creds_dict["_token_encrypted"] = True

View file

@ -173,13 +173,9 @@ async def gmail_callback(
# Validate required parameters for successful flow
if not code:
raise HTTPException(
status_code=400, detail="Missing authorization code"
)
raise HTTPException(status_code=400, detail="Missing authorization code")
if not state:
raise HTTPException(
status_code=400, detail="Missing state parameter"
)
raise HTTPException(status_code=400, detail="Missing state parameter")
# Validate and decode state with signature verification
state_manager = get_state_manager()
@ -209,7 +205,7 @@ async def gmail_callback(
# Encrypt sensitive credentials before storing
token_encryption = get_token_encryption()
# Encrypt sensitive fields: token, refresh_token, client_secret
if creds_dict.get("token"):
creds_dict["token"] = token_encryption.encrypt_token(creds_dict["token"])
@ -221,7 +217,7 @@ async def gmail_callback(
creds_dict["client_secret"] = token_encryption.encrypt_token(
creds_dict["client_secret"]
)
# Mark that credentials are encrypted for backward compatibility
creds_dict["_token_encrypted"] = True

View file

@ -4,7 +4,6 @@ Linear Connector OAuth Routes.
Handles OAuth 2.0 authentication flow for Linear connector.
"""
import json
import logging
from datetime import UTC, datetime, timedelta
from uuid import UUID
@ -89,9 +88,7 @@ async def connect_linear(space_id: int, user: User = Depends(current_active_user
raise HTTPException(status_code=400, detail="space_id is required")
if not config.LINEAR_CLIENT_ID:
raise HTTPException(
status_code=500, detail="Linear OAuth not configured."
)
raise HTTPException(status_code=500, detail="Linear OAuth not configured.")
if not config.SECRET_KEY:
raise HTTPException(
@ -115,9 +112,7 @@ async def connect_linear(space_id: int, user: User = Depends(current_active_user
auth_url = f"{AUTHORIZATION_URL}?{urlencode(auth_params)}"
logger.info(
f"Generated Linear OAuth URL for user {user.id}, space {space_id}"
)
logger.info(f"Generated Linear OAuth URL for user {user.id}, space {space_id}")
return {"auth_url": auth_url}
except Exception as e:
@ -162,7 +157,7 @@ async def linear_callback(
except Exception:
# If state is invalid, we'll redirect without space_id
logger.warning("Failed to validate state in error handler")
# Redirect to frontend with error parameter
if space_id:
return RedirectResponse(
@ -172,16 +167,12 @@ async def linear_callback(
return RedirectResponse(
url=f"{config.NEXT_FRONTEND_URL}/dashboard?error=linear_oauth_denied"
)
# Validate required parameters for successful flow
if not code:
raise HTTPException(
status_code=400, detail="Missing authorization code"
)
raise HTTPException(status_code=400, detail="Missing authorization code")
if not state:
raise HTTPException(
status_code=400, detail="Missing state parameter"
)
raise HTTPException(status_code=400, detail="Missing state parameter")
# Validate and decode state with signature verification
state_manager = get_state_manager()
@ -242,7 +233,7 @@ async def linear_callback(
token_encryption = get_token_encryption()
access_token = token_json.get("access_token")
refresh_token = token_json.get("refresh_token")
if not access_token:
raise HTTPException(
status_code=400, detail="No access token received from Linear"
@ -337,4 +328,3 @@ async def linear_callback(
raise HTTPException(
status_code=500, detail=f"Failed to complete Linear OAuth: {e!s}"
) from e

View file

@ -4,7 +4,6 @@ Notion Connector OAuth Routes.
Handles OAuth 2.0 authentication flow for Notion connector.
"""
import json
import logging
from uuid import UUID
@ -85,9 +84,7 @@ async def connect_notion(space_id: int, user: User = Depends(current_active_user
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."
)
raise HTTPException(status_code=500, detail="Notion OAuth not configured.")
if not config.SECRET_KEY:
raise HTTPException(
@ -111,9 +108,7 @@ async def connect_notion(space_id: int, user: User = Depends(current_active_user
auth_url = f"{AUTHORIZATION_URL}?{urlencode(auth_params)}"
logger.info(
f"Generated Notion OAuth URL for user {user.id}, space {space_id}"
)
logger.info(f"Generated Notion OAuth URL for user {user.id}, space {space_id}")
return {"auth_url": auth_url}
except Exception as e:
@ -158,7 +153,7 @@ async def notion_callback(
except Exception:
# If state is invalid, we'll redirect without space_id
logger.warning("Failed to validate state in error handler")
# Redirect to frontend with error parameter
if space_id:
return RedirectResponse(
@ -168,16 +163,12 @@ async def notion_callback(
return RedirectResponse(
url=f"{config.NEXT_FRONTEND_URL}/dashboard?error=notion_oauth_denied"
)
# Validate required parameters for successful flow
if not code:
raise HTTPException(
status_code=400, detail="Missing authorization code"
)
raise HTTPException(status_code=400, detail="Missing authorization code")
if not state:
raise HTTPException(
status_code=400, detail="Missing state parameter"
)
raise HTTPException(status_code=400, detail="Missing state parameter")
# Validate and decode state with signature verification
state_manager = get_state_manager()
@ -325,4 +316,3 @@ async def notion_callback(
raise HTTPException(
status_code=500, detail=f"Failed to complete Notion OAuth: {e!s}"
) from e

View file

@ -12,13 +12,13 @@ from app.routes.airtable_add_connector_route import refresh_airtable_token
from app.schemas.airtable_auth_credentials import AirtableAuthCredentialsBase
from app.services.llm_service import get_user_long_context_llm
from app.services.task_logging_service import TaskLoggingService
from app.utils.oauth_security import TokenEncryption
from app.utils.document_converters import (
create_document_chunks,
generate_content_hash,
generate_document_summary,
generate_unique_identifier_hash,
)
from app.utils.oauth_security import TokenEncryption
from .base import (
calculate_date_range,
@ -86,29 +86,35 @@ async def index_airtable_records(
return 0, f"Connector with ID {connector_id} not found"
# Create credentials from connector config
config_data = connector.config.copy() # Work with a copy to avoid modifying original
config_data = (
connector.config.copy()
) # Work with a copy to avoid modifying original
# Decrypt tokens if they are encrypted (for backward compatibility)
token_encrypted = config_data.get("_token_encrypted", False)
if token_encrypted and config.SECRET_KEY:
try:
token_encryption = TokenEncryption(config.SECRET_KEY)
# Decrypt access_token
if config_data.get("access_token"):
if token_encryption.is_encrypted(config_data["access_token"]):
config_data["access_token"] = token_encryption.decrypt_token(
config_data["access_token"]
)
logger.info(f"Decrypted Airtable access token for connector {connector_id}")
logger.info(
f"Decrypted Airtable access token for connector {connector_id}"
)
# Decrypt refresh_token if present
if config_data.get("refresh_token"):
if token_encryption.is_encrypted(config_data["refresh_token"]):
config_data["refresh_token"] = token_encryption.decrypt_token(
config_data["refresh_token"]
)
logger.info(f"Decrypted Airtable refresh token for connector {connector_id}")
logger.info(
f"Decrypted Airtable refresh token for connector {connector_id}"
)
except Exception as e:
await task_logger.log_task_failure(
log_entry,
@ -117,7 +123,7 @@ async def index_airtable_records(
{"error_type": "TokenDecryptionError"},
)
return 0, f"Failed to decrypt Airtable tokens: {e!s}"
try:
credentials = AirtableAuthCredentialsBase.from_dict(config_data)
except Exception as e:

View file

@ -8,7 +8,6 @@ from google.oauth2.credentials import Credentials
from sqlalchemy.exc import SQLAlchemyError
from sqlalchemy.ext.asyncio import AsyncSession
from app.config import config
from app.connectors.google_calendar_connector import GoogleCalendarConnector
from app.db import Document, DocumentType, SearchSourceConnectorType
from app.services.llm_service import get_user_long_context_llm
@ -85,7 +84,7 @@ async def index_google_calendar_events(
# Get the Google Calendar credentials from the connector config
config_data = connector.config
# Decrypt sensitive credentials if encrypted (for backward compatibility)
from app.config import config
from app.utils.oauth_security import TokenEncryption
@ -94,7 +93,7 @@ async def index_google_calendar_events(
if token_encrypted and config.SECRET_KEY:
try:
token_encryption = TokenEncryption(config.SECRET_KEY)
# Decrypt sensitive fields
if config_data.get("token"):
config_data["token"] = token_encryption.decrypt_token(
@ -108,7 +107,7 @@ async def index_google_calendar_events(
config_data["client_secret"] = token_encryption.decrypt_token(
config_data["client_secret"]
)
logger.info(
f"Decrypted Google Calendar credentials for connector {connector_id}"
)

View file

@ -8,7 +8,6 @@ from google.oauth2.credentials import Credentials
from sqlalchemy.exc import SQLAlchemyError
from sqlalchemy.ext.asyncio import AsyncSession
from app.config import config
from app.connectors.google_gmail_connector import GoogleGmailConnector
from app.db import (
Document,
@ -90,7 +89,7 @@ async def index_google_gmail_messages(
# Get the Google Gmail credentials from the connector config
config_data = connector.config
# Decrypt sensitive credentials if encrypted (for backward compatibility)
from app.config import config
from app.utils.oauth_security import TokenEncryption
@ -99,7 +98,7 @@ async def index_google_gmail_messages(
if token_encrypted and config.SECRET_KEY:
try:
token_encryption = TokenEncryption(config.SECRET_KEY)
# Decrypt sensitive fields
if config_data.get("token"):
config_data["token"] = token_encryption.decrypt_token(
@ -113,7 +112,7 @@ async def index_google_gmail_messages(
config_data["client_secret"] = token_encryption.decrypt_token(
config_data["client_secret"]
)
logger.info(
f"Decrypted Google Gmail credentials for connector {connector_id}"
)

View file

@ -7,7 +7,6 @@ from datetime import datetime
from sqlalchemy.exc import SQLAlchemyError
from sqlalchemy.ext.asyncio import AsyncSession
from app.config import config
from app.connectors.linear_connector import LinearConnector
from app.db import Document, DocumentType, SearchSourceConnectorType
from app.services.llm_service import get_user_long_context_llm
@ -114,7 +113,9 @@ async def index_linear_issues(
):
try:
token_encryption = TokenEncryption(config.SECRET_KEY)
linear_access_token = token_encryption.decrypt_token(linear_access_token)
linear_access_token = token_encryption.decrypt_token(
linear_access_token
)
logger.info(
f"Decrypted Linear access token for connector {connector_id}"
)

View file

@ -107,11 +107,16 @@ async def index_notion_pages(
# Decrypt token if it's encrypted (for backward compatibility)
token_encrypted = connector.config.get("_token_encrypted", False)
if token_encrypted or (config.SECRET_KEY and TokenEncryption(config.SECRET_KEY).is_encrypted(notion_token)):
if token_encrypted or (
config.SECRET_KEY
and TokenEncryption(config.SECRET_KEY).is_encrypted(notion_token)
):
try:
token_encryption = TokenEncryption(config.SECRET_KEY)
notion_token = token_encryption.decrypt_token(notion_token)
logger.info(f"Decrypted Notion access token for connector {connector_id}")
logger.info(
f"Decrypted Notion access token for connector {connector_id}"
)
except Exception as e:
await task_logger.log_task_failure(
log_entry,

View file

@ -35,7 +35,9 @@ class OAuthStateManager:
self.secret_key = secret_key
self.max_age_seconds = max_age_seconds
def generate_secure_state(self, space_id: int, user_id: UUID, **extra_fields) -> str:
def generate_secure_state(
self, space_id: int, user_id: UUID, **extra_fields
) -> str:
"""
Generate cryptographically signed state parameter.
@ -53,7 +55,7 @@ class OAuthStateManager:
"user_id": str(user_id),
"timestamp": timestamp,
}
# Add any extra fields (e.g., code_verifier for PKCE)
state_payload.update(extra_fields)
@ -97,9 +99,7 @@ class OAuthStateManager:
# Verify signature exists
signature = data.pop("signature", None)
if not signature:
raise HTTPException(
status_code=400, detail="Missing state signature"
)
raise HTTPException(status_code=400, detail="Missing state signature")
# Verify signature
payload_str = json.dumps(data, sort_keys=True)
@ -120,9 +120,7 @@ class OAuthStateManager:
age = current_time - timestamp
if age < 0:
raise HTTPException(
status_code=400, detail="Invalid state timestamp"
)
raise HTTPException(status_code=400, detail="Invalid state timestamp")
if age > self.max_age_seconds:
raise HTTPException(
@ -147,15 +145,11 @@ class TokenEncryption:
raise ValueError("secret_key is required for token encryption")
# Derive Fernet key from secret using SHA256
# Note: In production, consider using HKDF for key derivation
key = base64.urlsafe_b64encode(
hashlib.sha256(secret_key.encode()).digest()
)
key = base64.urlsafe_b64encode(hashlib.sha256(secret_key.encode()).digest())
try:
self.cipher = Fernet(key)
except Exception as e:
raise ValueError(
f"Failed to initialize encryption cipher: {e!s}"
) from e
raise ValueError(f"Failed to initialize encryption cipher: {e!s}") from e
def encrypt_token(self, token: str) -> str:
"""

View file

@ -1,13 +1,7 @@
"use client";
import { IconBrandYoutube } from "@tabler/icons-react";
import {
differenceInDays,
differenceInMinutes,
format,
isToday,
isYesterday,
} from "date-fns";
import { differenceInDays, differenceInMinutes, format, isToday, isYesterday } from "date-fns";
import { FileText, Loader2 } from "lucide-react";
import type { FC } from "react";
import { Button } from "@/components/ui/button";

View file

@ -46,7 +46,7 @@ export const IndexingConfigurationView: FC<IndexingConfigurationViewProps> = ({
}) => {
const searchParams = useSearchParams();
const isFromOAuth = searchParams.get("view") === "configure";
// Get connector-specific config component
const ConnectorConfigComponent = useMemo(
() => (connector ? getConnectorConfigComponent(connector.connector_type) : null),

View file

@ -1,12 +1,6 @@
"use client";
import {
differenceInDays,
differenceInMinutes,
format,
isToday,
isYesterday,
} from "date-fns";
import { differenceInDays, differenceInMinutes, format, isToday, isYesterday } from "date-fns";
import { ArrowRight, Cable, Loader2 } from "lucide-react";
import { useRouter } from "next/navigation";
import type { FC } from "react";

View file

@ -99,7 +99,7 @@ const DocumentUploadPopupContent: FC<{
<Dialog open={isOpen} onOpenChange={onOpenChange}>
<DialogContent className="max-w-4xl w-[95vw] sm:w-full max-h-[calc(100vh-2rem)] sm:h-[85vh] flex flex-col p-0 gap-0 overflow-hidden border border-border bg-muted text-foreground [&>button]:right-3 sm:[&>button]:right-12 [&>button]:top-4 sm:[&>button]:top-10 [&>button]:opacity-80 hover:[&>button]:opacity-100 [&>button]:z-[100] [&>button_svg]:size-4 sm:[&>button_svg]:size-5">
<DialogTitle className="sr-only">Upload Document</DialogTitle>
{/* Fixed Header */}
<div className="flex-shrink-0 px-4 sm:px-12 pt-6 sm:pt-10 transition-shadow duration-200 relative z-10">
{/* Upload header */}
@ -120,8 +120,8 @@ const DocumentUploadPopupContent: FC<{
<div className="flex-1 min-h-0 relative overflow-hidden">
<div className={`h-full ${isAccordionExpanded ? "overflow-y-auto" : ""}`}>
<div className="px-6 sm:px-12 pb-5 sm:pb-16">
<DocumentUploadTab
searchSpaceId={searchSpaceId}
<DocumentUploadTab
searchSpaceId={searchSpaceId}
onSuccess={handleSuccess}
onAccordionStateChange={setIsAccordionExpanded}
/>

View file

@ -110,7 +110,11 @@ const FILE_TYPE_CONFIG: Record<string, Record<string, string[]>> = {
const cardClass = "border border-border bg-slate-400/5 dark:bg-white/5";
export function DocumentUploadTab({ searchSpaceId, onSuccess, onAccordionStateChange }: DocumentUploadTabProps) {
export function DocumentUploadTab({
searchSpaceId,
onSuccess,
onAccordionStateChange,
}: DocumentUploadTabProps) {
const t = useTranslations("upload_documents");
const router = useRouter();
const [files, setFiles] = useState<File[]>([]);
@ -157,10 +161,13 @@ export function DocumentUploadTab({ searchSpaceId, onSuccess, onAccordionStateCh
const totalFileSize = files.reduce((total, file) => total + file.size, 0);
// Track accordion state changes
const handleAccordionChange = useCallback((value: string) => {
setAccordionValue(value);
onAccordionStateChange?.(value === "supported-file-types");
}, [onAccordionStateChange]);
const handleAccordionChange = useCallback(
(value: string) => {
setAccordionValue(value);
onAccordionStateChange?.(value === "supported-file-types");
},
[onAccordionStateChange]
);
const handleUpload = async () => {
setUploadProgress(0);
@ -202,7 +209,9 @@ export function DocumentUploadTab({ searchSpaceId, onSuccess, onAccordionStateCh
>
<Alert className="border border-border bg-slate-400/5 dark:bg-white/5 flex items-start gap-3 [&>svg]:relative [&>svg]:left-0 [&>svg]:top-0 [&>svg~*]:pl-0">
<Info className="h-4 w-4 shrink-0 mt-0.5" />
<AlertDescription className="text-xs sm:text-sm leading-relaxed pt-0.5">{t("file_size_limit")}</AlertDescription>
<AlertDescription className="text-xs sm:text-sm leading-relaxed pt-0.5">
{t("file_size_limit")}
</AlertDescription>
</Alert>
<Card className={`relative overflow-hidden ${cardClass}`}>