From 4ce4dfe9b8529442fbff25f8e7c0a78592a6e2a4 Mon Sep 17 00:00:00 2001 From: "DESKTOP-RTLN3BA\\$punk" Date: Tue, 16 Sep 2025 21:57:55 -0700 Subject: [PATCH 01/18] fix: biome check --- .../components/settings/model-config-manager.tsx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/surfsense_web/components/settings/model-config-manager.tsx b/surfsense_web/components/settings/model-config-manager.tsx index a7f5d2dec..bc59532d1 100644 --- a/surfsense_web/components/settings/model-config-manager.tsx +++ b/surfsense_web/components/settings/model-config-manager.tsx @@ -112,12 +112,12 @@ const LLM_PROVIDERS = [ example: "meta/llama-2-70b-chat", description: "Run models via API", }, - { - value: "OPENROUTER", - label: "OpenRouter", - example: "anthropic/claude-opus-4.1, openai/gpt-5", - description: "API gateway and LLM marketplace that provides unified access ", - }, + { + value: "OPENROUTER", + label: "OpenRouter", + example: "anthropic/claude-opus-4.1, openai/gpt-5", + description: "API gateway and LLM marketplace that provides unified access ", + }, { value: "CUSTOM", label: "Custom Provider", From 2fd77b74c9906f0904f0481d7011ba98cf577742 Mon Sep 17 00:00:00 2001 From: samkul-swe Date: Thu, 18 Sep 2025 14:52:46 -0700 Subject: [PATCH 02/18] fix for 322 - shows relevant messages and toasts --- surfsense_web/app/login/LocalLoginForm.tsx | 145 +++++++++++++-- surfsense_web/app/login/page.tsx | 131 ++++++++++++- surfsense_web/app/register/page.tsx | 161 ++++++++++++++-- surfsense_web/lib/auth-errors.ts | 207 +++++++++++++++++++++ 4 files changed, 603 insertions(+), 41 deletions(-) create mode 100644 surfsense_web/lib/auth-errors.ts diff --git a/surfsense_web/app/login/LocalLoginForm.tsx b/surfsense_web/app/login/LocalLoginForm.tsx index 7cc50b28a..2a4d9239a 100644 --- a/surfsense_web/app/login/LocalLoginForm.tsx +++ b/surfsense_web/app/login/LocalLoginForm.tsx @@ -2,11 +2,15 @@ import Link from "next/link"; import { useRouter } from "next/navigation"; import { useEffect, useState } from "react"; +import { motion, AnimatePresence } from "framer-motion"; +import { toast } from "sonner"; +import { getAuthErrorDetails, shouldRetry, isNetworkError } from "@/lib/auth-errors"; export function LocalLoginForm() { const [username, setUsername] = useState(""); const [password, setPassword] = useState(""); - const [error, setError] = useState(""); + const [error, setError] = useState(null); + const [errorTitle, setErrorTitle] = useState(null); const [isLoading, setIsLoading] = useState(false); const [authType, setAuthType] = useState(null); const router = useRouter(); @@ -19,7 +23,11 @@ export function LocalLoginForm() { const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); setIsLoading(true); - setError(""); + setError(null); // Clear any previous errors + setErrorTitle(null); + + // Show loading toast + const loadingToast = toast.loading("Signing you in..."); try { // Create form data for the API request @@ -42,13 +50,54 @@ export function LocalLoginForm() { const data = await response.json(); if (!response.ok) { - throw new Error(data.detail || "Failed to login"); + throw new Error(data.detail || `HTTP ${response.status}`); } - router.push(`/auth/callback?token=${data.access_token}`); + // Success toast + toast.success("Login successful!", { + id: loadingToast, + description: "Redirecting to dashboard...", + duration: 2000, + }); + + // Small delay to show success message + setTimeout(() => { + router.push(`/auth/callback?token=${data.access_token}`); + }, 500); + } catch (err) { - const errorMessage = err instanceof Error ? err.message : "An error occurred during login"; - setError(errorMessage); + // Use auth-errors utility to get proper error details + let errorCode = "UNKNOWN_ERROR"; + + if (err instanceof Error) { + errorCode = err.message; + } else if (isNetworkError(err)) { + errorCode = "NETWORK_ERROR"; + } + + // Get detailed error information from auth-errors utility + const errorDetails = getAuthErrorDetails(errorCode); + + // Set persistent error display + setErrorTitle(errorDetails.title); + setError(errorDetails.description); + + // Show error toast with conditional retry action + const toastOptions: any = { + id: loadingToast, + description: errorDetails.description, + duration: 6000, + }; + + // Add retry action if the error is retryable + if (shouldRetry(errorCode)) { + toastOptions.action = { + label: "Retry", + onClick: () => handleSubmit(e), + }; + } + + toast.error(errorDetails.title, toastOptions); } finally { setIsLoading(false); } @@ -57,11 +106,69 @@ export function LocalLoginForm() { return (
- {error && ( -
- {error} -
- )} + {/* Error Display */} + + {error && errorTitle && ( + +
+ + Error Icon + + + + +
+

{errorTitle}

+

+ {error} +

+
+ +
+
+ )} +
@@ -93,14 +205,19 @@ export function LocalLoginForm() { required value={password} onChange={(e) => setPassword(e.target.value)} - className="mt-1 block w-full rounded-md border border-gray-300 bg-white px-3 py-2 shadow-sm focus:border-blue-500 focus:outline-none focus:ring-blue-500 dark:border-gray-700 dark:bg-gray-800 dark:text-white" + className={`mt-1 block w-full rounded-md border px-3 py-2 shadow-sm focus:outline-none focus:ring-2 focus:ring-offset-2 dark:bg-gray-800 dark:text-white transition-colors ${ + error + ? "border-red-300 focus:border-red-500 focus:ring-red-500 dark:border-red-700" + : "border-gray-300 focus:border-blue-500 focus:ring-blue-500 dark:border-gray-700" + }`} + disabled={isLoading} />
diff --git a/surfsense_web/app/login/page.tsx b/surfsense_web/app/login/page.tsx index 1957c22bf..209dc745a 100644 --- a/surfsense_web/app/login/page.tsx +++ b/surfsense_web/app/login/page.tsx @@ -3,6 +3,9 @@ import { Loader2 } from "lucide-react"; import { useSearchParams } from "next/navigation"; import { Suspense, useEffect, useState } from "react"; +import { motion, AnimatePresence } from "framer-motion"; +import { toast } from "sonner"; +import { getAuthErrorDetails, shouldRetry } from "@/lib/auth-errors"; import { Logo } from "@/components/Logo"; import { AmbientBackground } from "./AmbientBackground"; import { GoogleLoginButton } from "./GoogleLoginButton"; @@ -10,14 +13,70 @@ import { LocalLoginForm } from "./LocalLoginForm"; function LoginContent() { const [authType, setAuthType] = useState(null); - const [registrationSuccess, setRegistrationSuccess] = useState(false); const [isLoading, setIsLoading] = useState(true); + const [urlError, setUrlError] = useState<{ title: string; message: string } | null>(null); const searchParams = useSearchParams(); useEffect(() => { - // Check if the user was redirected from registration - if (searchParams.get("registered") === "true") { - setRegistrationSuccess(true); + // Check for various URL parameters that might indicate success or error states + const registered = searchParams.get("registered"); + const error = searchParams.get("error"); + const message = searchParams.get("message"); + const logout = searchParams.get("logout"); + + // Show registration success message + if (registered === "true") { + toast.success("Registration successful!", { + description: "You can now sign in with your credentials", + duration: 5000, + }); + } + + // Show logout confirmation + if (logout === "true") { + toast.success("Logged out successfully", { + description: "You have been securely logged out", + duration: 3000, + }); + } + + // Show error messages from OAuth or other flows using auth-errors utility + if (error) { + // Use the auth-errors utility to get proper error details + const errorDetails = getAuthErrorDetails(error); + + // If we have a custom message from URL params, use it as description + const errorDescription = message ? decodeURIComponent(message) : errorDetails.description; + + // Set persistent error display + setUrlError({ + title: errorDetails.title, + message: errorDescription + }); + + // Show toast with conditional retry action + const toastOptions: any = { + description: errorDescription, + duration: 6000, + }; + + // Add retry action if the error is retryable + if (shouldRetry(error)) { + toastOptions.action = { + label: "Retry", + onClick: () => window.location.reload(), + }; + } + + toast.error(errorDetails.title, toastOptions); + } + + // Show general messages + if (message && !error && !registered && !logout) { + toast.info("Notice", { + description: decodeURIComponent(message), + duration: 4000, + }); } // Get the auth type from environment variables @@ -54,11 +113,65 @@ function LoginContent() { Sign In - {registrationSuccess && ( -
- Registration successful! You can now sign in with your credentials. -
- )} + {/* URL Error Display */} + + {urlError && ( + +
+ + Error Icon + + + + +
+

{urlError.title}

+

+ {urlError.message} +

+
+ +
+
+ )} +
diff --git a/surfsense_web/app/register/page.tsx b/surfsense_web/app/register/page.tsx index 1a89dbb4e..28559c991 100644 --- a/surfsense_web/app/register/page.tsx +++ b/surfsense_web/app/register/page.tsx @@ -3,6 +3,9 @@ import Link from "next/link"; import { useRouter } from "next/navigation"; import { useEffect, useState } from "react"; +import { motion, AnimatePresence } from "framer-motion"; +import { toast } from "sonner"; +import { getAuthErrorDetails, shouldRetry, isNetworkError } from "@/lib/auth-errors"; import { Logo } from "@/components/Logo"; import { AmbientBackground } from "../login/AmbientBackground"; @@ -10,7 +13,8 @@ export default function RegisterPage() { const [email, setEmail] = useState(""); const [password, setPassword] = useState(""); const [confirmPassword, setConfirmPassword] = useState(""); - const [error, setError] = useState(""); + const [error, setError] = useState(null); + const [errorTitle, setErrorTitle] = useState(null); const [isLoading, setIsLoading] = useState(false); const router = useRouter(); @@ -28,11 +32,20 @@ export default function RegisterPage() { // Form validation if (password !== confirmPassword) { setError("Passwords do not match"); + setErrorTitle("Password Mismatch"); + toast.error("Password Mismatch", { + description: "The passwords you entered do not match", + duration: 4000, + }); return; } setIsLoading(true); - setError(""); + setError(null); // Clear any previous errors + setErrorTitle(null); + + // Show loading toast + const loadingToast = toast.loading("Creating your account..."); try { const response = await fetch(`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/auth/register`, { @@ -52,15 +65,54 @@ export default function RegisterPage() { const data = await response.json(); if (!response.ok) { - throw new Error(data.detail || "Registration failed"); + throw new Error(data.detail || `HTTP ${response.status}`); } - // Redirect to login page after successful registration - router.push("/login?registered=true"); - } catch (err: unknown) { - const errorMessage = - err instanceof Error ? err.message : "An error occurred during registration"; - setError(errorMessage); + // Success toast + toast.success("Account created successfully!", { + id: loadingToast, + description: "Redirecting to login page...", + duration: 2000, + }); + + // Small delay to show success message + setTimeout(() => { + router.push("/login?registered=true"); + }, 500); + + } catch (err) { + // Use auth-errors utility to get proper error details + let errorCode = "UNKNOWN_ERROR"; + + if (err instanceof Error) { + errorCode = err.message; + } else if (isNetworkError(err)) { + errorCode = "NETWORK_ERROR"; + } + + // Get detailed error information from auth-errors utility + const errorDetails = getAuthErrorDetails(errorCode); + + // Set persistent error display + setErrorTitle(errorDetails.title); + setError(errorDetails.description); + + // Show error toast with conditional retry action + const toastOptions: any = { + id: loadingToast, + description: errorDetails.description, + duration: 6000, + }; + + // Add retry action if the error is retryable + if (shouldRetry(errorCode)) { + toastOptions.action = { + label: "Retry", + onClick: () => handleSubmit(e), + }; + } + + toast.error(errorDetails.title, toastOptions); } finally { setIsLoading(false); } @@ -77,11 +129,69 @@ export default function RegisterPage() {
- {error && ( -
- {error} -
- )} + {/* Enhanced Error Display */} + + {error && errorTitle && ( + +
+ + Error Icon + + + + +
+

{errorTitle}

+

+ {error} +

+
+ +
+
+ )} +
@@ -113,7 +228,12 @@ export default function RegisterPage() { required value={password} onChange={(e) => setPassword(e.target.value)} - className="mt-1 block w-full rounded-md border border-gray-300 bg-white px-3 py-2 shadow-sm focus:border-blue-500 focus:outline-none focus:ring-blue-500 dark:border-gray-700 dark:bg-gray-800 dark:text-white" + className={`mt-1 block w-full rounded-md border px-3 py-2 shadow-sm focus:outline-none focus:ring-2 focus:ring-offset-2 dark:bg-gray-800 dark:text-white transition-colors ${ + error + ? "border-red-300 focus:border-red-500 focus:ring-red-500 dark:border-red-700" + : "border-gray-300 focus:border-blue-500 focus:ring-blue-500 dark:border-gray-700" + }`} + disabled={isLoading} />
@@ -130,14 +250,19 @@ export default function RegisterPage() { required value={confirmPassword} onChange={(e) => setConfirmPassword(e.target.value)} - className="mt-1 block w-full rounded-md border border-gray-300 bg-white px-3 py-2 shadow-sm focus:border-blue-500 focus:outline-none focus:ring-blue-500 dark:border-gray-700 dark:bg-gray-800 dark:text-white" + className={`mt-1 block w-full rounded-md border px-3 py-2 shadow-sm focus:outline-none focus:ring-2 focus:ring-offset-2 dark:bg-gray-800 dark:text-white transition-colors ${ + error + ? "border-red-300 focus:border-red-500 focus:ring-red-500 dark:border-red-700" + : "border-gray-300 focus:border-blue-500 focus:ring-blue-500 dark:border-gray-700" + }`} + disabled={isLoading} /> diff --git a/surfsense_web/lib/auth-errors.ts b/surfsense_web/lib/auth-errors.ts new file mode 100644 index 000000000..1858bd45c --- /dev/null +++ b/surfsense_web/lib/auth-errors.ts @@ -0,0 +1,207 @@ +/** + * Authentication error messages and handling utilities + */ + +interface AuthErrorMapping { + [key: string]: { + title: string; + description?: string; + }; +} + +const AUTH_ERROR_MESSAGES: AuthErrorMapping = { + // Common HTTP errors + "401": { + title: "Invalid credentials", + description: "Please check your email and password" + }, + "403": { + title: "Access denied", + description: "Your account may be suspended or restricted" + }, + "404": { + title: "Account not found", + description: "No account exists with this email address" + }, + "409": { + title: "Account conflict", + description: "An account with this email already exists" + }, + "429": { + title: "Too many attempts", + description: "Please wait before trying again" + }, + "500": { + title: "Server error", + description: "Something went wrong on our end. Please try again" + }, + "503": { + title: "Service unavailable", + description: "Login service is temporarily down" + }, + + // FastAPI specific errors + "LOGIN_BAD_CREDENTIALS": { + title: "Invalid credentials", + description: "The email or password you entered is incorrect" + }, + "LOGIN_USER_NOT_VERIFIED": { + title: "Account not verified", + description: "Please verify your email address before signing in" + }, + "USER_INACTIVE": { + title: "Account inactive", + description: "Your account has been deactivated. Contact support for assistance" + }, + "REGISTER_USER_ALREADY_EXISTS": { + title: "Account already exists", + description: "An account with this email address already exists" + }, + "REGISTER_INVALID_PASSWORD": { + title: "Invalid password", + description: "Password must meet security requirements" + }, + + // OAuth errors + "access_denied": { + title: "Access denied", + description: "You denied access or cancelled the login process" + }, + "invalid_request": { + title: "Invalid request", + description: "The login request was malformed" + }, + "unauthorized_client": { + title: "Authentication failed", + description: "The application is not authorized to perform this action" + }, + "unsupported_response_type": { + title: "Login method not supported", + description: "This login method is not currently available" + }, + "invalid_scope": { + title: "Invalid permissions", + description: "The requested permissions are not valid" + }, + "server_error": { + title: "Server error", + description: "An error occurred on the authentication server" + }, + "temporarily_unavailable": { + title: "Service unavailable", + description: "Login is temporarily unavailable. Please try again later" + }, + + // Network errors + "NETWORK_ERROR": { + title: "Connection failed", + description: "Please check your internet connection and try again" + }, + "TIMEOUT": { + title: "Request timeout", + description: "The login request took too long. Please try again" + }, + + // Generic fallbacks + "UNKNOWN_ERROR": { + title: "Login failed", + description: "An unexpected error occurred. Please try again" + } +}; + +/** + * Get a user-friendly error message for authentication errors + * @param errorCode - The error code or message from the API + * @param returnTitle - Whether to return just the title or full description + * @returns Formatted error message + */ +export function getAuthErrorMessage(errorCode: string, returnTitle: boolean = false): string { + if (!errorCode) { + const fallback = AUTH_ERROR_MESSAGES.UNKNOWN_ERROR; + return returnTitle ? fallback.title : fallback.description || fallback.title; + } + + // Clean up the error code + const cleanErrorCode = errorCode.trim().toUpperCase(); + + // Try exact match first + let errorInfo = AUTH_ERROR_MESSAGES[cleanErrorCode] || AUTH_ERROR_MESSAGES[errorCode]; + + // Try partial matches for HTTP status codes + if (!errorInfo) { + const statusCodeMatch = errorCode.match(/(\d{3})/); + if (statusCodeMatch) { + errorInfo = AUTH_ERROR_MESSAGES[statusCodeMatch[1]]; + } + } + + // Try partial matches for common error patterns + if (!errorInfo) { + const patterns = [ + { pattern: /credential|password|email/i, code: "LOGIN_BAD_CREDENTIALS" }, + { pattern: /verify|verification/i, code: "LOGIN_USER_NOT_VERIFIED" }, + { pattern: /inactive|disabled|suspended/i, code: "USER_INACTIVE" }, + { pattern: /exists|duplicate/i, code: "REGISTER_USER_ALREADY_EXISTS" }, + { pattern: /network|connection/i, code: "NETWORK_ERROR" }, + { pattern: /timeout/i, code: "TIMEOUT" }, + { pattern: /rate|limit|many/i, code: "429" }, + ]; + + for (const { pattern, code } of patterns) { + if (pattern.test(errorCode)) { + errorInfo = AUTH_ERROR_MESSAGES[code]; + break; + } + } + } + + // Fallback to unknown error + if (!errorInfo) { + errorInfo = AUTH_ERROR_MESSAGES.UNKNOWN_ERROR; + } + + return returnTitle ? errorInfo.title : errorInfo.description || errorInfo.title; +} + +/** + * Get both title and description for an error + * @param errorCode - The error code or message from the API + * @returns Object with title and description + */ +export function getAuthErrorDetails(errorCode: string): { title: string; description: string } { + const title = getAuthErrorMessage(errorCode, true); + const description = getAuthErrorMessage(errorCode, false); + + return { title, description }; +} + +/** + * Check if an error is a network-related error + * @param error - The error object or message + * @returns True if it's a network error + */ +export function isNetworkError(error: unknown): boolean { + if (error instanceof TypeError && error.message.includes('fetch')) { + return true; + } + + if (typeof error === 'string') { + return /network|connection|fetch|cors/i.test(error); + } + + return false; +} + +/** + * Check if an error should trigger a retry action + * @param errorCode - The error code or message + * @returns True if retry is recommended + */ +export function shouldRetry(errorCode: string): boolean { + const retryableCodes = ['500', '503', '429', 'NETWORK_ERROR', 'TIMEOUT', 'server_error', 'temporarily_unavailable']; + + return retryableCodes.some(code => + errorCode.includes(code) || + errorCode.toUpperCase().includes(code) + ); +} From c2897d7fbecc07ef0da4772161c09a1bb24e7500 Mon Sep 17 00:00:00 2001 From: "DESKTOP-RTLN3BA\\$punk" Date: Thu, 18 Sep 2025 17:46:51 -0700 Subject: [PATCH 03/18] fix: linting and document limit --- .../app/routes/documents_routes.py | 2 +- surfsense_web/app/login/LocalLoginForm.tsx | 21 +- surfsense_web/app/login/page.tsx | 18 +- surfsense_web/app/register/page.tsx | 21 +- surfsense_web/lib/auth-errors.ts | 325 +++++++++--------- 5 files changed, 193 insertions(+), 194 deletions(-) diff --git a/surfsense_backend/app/routes/documents_routes.py b/surfsense_backend/app/routes/documents_routes.py index b75c56663..3ea1e8e61 100644 --- a/surfsense_backend/app/routes/documents_routes.py +++ b/surfsense_backend/app/routes/documents_routes.py @@ -157,7 +157,7 @@ async def create_documents_file_upload( @router.get("/documents/", response_model=list[DocumentRead]) async def read_documents( skip: int = 0, - limit: int = 3000, + limit: int = 300, search_space_id: int | None = None, session: AsyncSession = Depends(get_async_session), user: User = Depends(current_active_user), diff --git a/surfsense_web/app/login/LocalLoginForm.tsx b/surfsense_web/app/login/LocalLoginForm.tsx index 2a4d9239a..b464bea98 100644 --- a/surfsense_web/app/login/LocalLoginForm.tsx +++ b/surfsense_web/app/login/LocalLoginForm.tsx @@ -1,10 +1,10 @@ "use client"; +import { AnimatePresence, motion } from "framer-motion"; import Link from "next/link"; import { useRouter } from "next/navigation"; import { useEffect, useState } from "react"; -import { motion, AnimatePresence } from "framer-motion"; import { toast } from "sonner"; -import { getAuthErrorDetails, shouldRetry, isNetworkError } from "@/lib/auth-errors"; +import { getAuthErrorDetails, isNetworkError, shouldRetry } from "@/lib/auth-errors"; export function LocalLoginForm() { const [username, setUsername] = useState(""); @@ -64,31 +64,30 @@ export function LocalLoginForm() { setTimeout(() => { router.push(`/auth/callback?token=${data.access_token}`); }, 500); - } catch (err) { // Use auth-errors utility to get proper error details let errorCode = "UNKNOWN_ERROR"; - + if (err instanceof Error) { errorCode = err.message; } else if (isNetworkError(err)) { errorCode = "NETWORK_ERROR"; } - + // Get detailed error information from auth-errors utility const errorDetails = getAuthErrorDetails(errorCode); - + // Set persistent error display setErrorTitle(errorDetails.title); setError(errorDetails.description); - + // Show error toast with conditional retry action const toastOptions: any = { id: loadingToast, description: errorDetails.description, duration: 6000, }; - + // Add retry action if the error is retryable if (shouldRetry(errorCode)) { toastOptions.action = { @@ -96,7 +95,7 @@ export function LocalLoginForm() { onClick: () => handleSubmit(e), }; } - + toast.error(errorDetails.title, toastOptions); } finally { setIsLoading(false); @@ -136,9 +135,7 @@ export function LocalLoginForm() {

{errorTitle}

-

- {error} -

+

{error}

+ + + + + + ) : ( + /* Success Card */ + + + ✅ Your Luma account is successfully connected! + + + )} + + {/* Help Section */} + {!doesConnectorExist && ( + + + How It Works + + +
+

1. Get Your API Key

+

+ Log into your Luma account and navigate to your account settings to generate an + API key. +

+
+
+

2. Enter Your API Key

+

+ Paste your API key in the field above. We'll use this to securely access your + events with read-only permissions. +

+
+
+
+ )} + + + ); +} diff --git a/surfsense_web/app/dashboard/[search_space_id]/connectors/add/page.tsx b/surfsense_web/app/dashboard/[search_space_id]/connectors/add/page.tsx index 9d44ba0e9..9887fccb5 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/connectors/add/page.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/connectors/add/page.tsx @@ -140,6 +140,13 @@ const connectorCategories: ConnectorCategory[] = [ icon: getConnectorIcon(EnumConnectorName.AIRTABLE_CONNECTOR, "h-6 w-6"), status: "available", }, + { + id: "luma-connector", + title: "Luma", + description: "Connect to Luma to search events", + icon: getConnectorIcon(EnumConnectorName.LUMA_CONNECTOR, "h-6 w-6"), + status: "available", + } ], }, { diff --git a/surfsense_web/components/dashboard-breadcrumb.tsx b/surfsense_web/components/dashboard-breadcrumb.tsx index 1bc7abd2a..9cf022e37 100644 --- a/surfsense_web/components/dashboard-breadcrumb.tsx +++ b/surfsense_web/components/dashboard-breadcrumb.tsx @@ -87,6 +87,7 @@ export function DashboardBreadcrumb() { "tavily-api": "Tavily API", "serper-api": "Serper API", "linkup-api": "LinkUp API", + "luma-connector": "Luma" }; const connectorLabel = connectorLabels[connectorType] || connectorType; diff --git a/surfsense_web/components/editConnector/types.ts b/surfsense_web/components/editConnector/types.ts index 38ffc36fd..5cbf15907 100644 --- a/surfsense_web/components/editConnector/types.ts +++ b/surfsense_web/components/editConnector/types.ts @@ -43,5 +43,6 @@ export const editConnectorSchema = z.object({ GOOGLE_CALENDAR_CLIENT_SECRET: z.string().optional(), GOOGLE_CALENDAR_REFRESH_TOKEN: z.string().optional(), GOOGLE_CALENDAR_CALENDAR_IDS: z.string().optional(), + LUMA_API_KEY: z.string().optional() }); export type EditConnectorFormValues = z.infer; diff --git a/surfsense_web/contracts/enums/connector.ts b/surfsense_web/contracts/enums/connector.ts index bc121e165..bd1277be4 100644 --- a/surfsense_web/contracts/enums/connector.ts +++ b/surfsense_web/contracts/enums/connector.ts @@ -13,4 +13,5 @@ export enum EnumConnectorName { GOOGLE_CALENDAR_CONNECTOR = "GOOGLE_CALENDAR_CONNECTOR", GOOGLE_GMAIL_CONNECTOR = "GOOGLE_GMAIL_CONNECTOR", AIRTABLE_CONNECTOR = "AIRTABLE_CONNECTOR", + LUMA_CONNECTOR = "LUMA_CONNECTOR" } diff --git a/surfsense_web/contracts/enums/connectorIcons.tsx b/surfsense_web/contracts/enums/connectorIcons.tsx index 9ca9c8816..c1571c011 100644 --- a/surfsense_web/contracts/enums/connectorIcons.tsx +++ b/surfsense_web/contracts/enums/connectorIcons.tsx @@ -13,6 +13,7 @@ import { IconTable, IconTicket, IconWorldWww, + IconSparkles, } from "@tabler/icons-react"; import { File, Globe, Link, Microscope, Search, Sparkles, Telescope, Webhook } from "lucide-react"; import { EnumConnectorName } from "./connector"; @@ -49,6 +50,8 @@ export const getConnectorIcon = (connectorType: EnumConnectorName | string, clas return ; case EnumConnectorName.CLICKUP_CONNECTOR: return ; + case EnumConnectorName.LUMA_CONNECTOR: + return ; // Additional cases for non-enum connector types case "YOUTUBE_VIDEO": return ; diff --git a/surfsense_web/hooks/use-document-by-chunk.ts b/surfsense_web/hooks/use-document-by-chunk.ts index df2b1a7e0..25dc9813a 100644 --- a/surfsense_web/hooks/use-document-by-chunk.ts +++ b/surfsense_web/hooks/use-document-by-chunk.ts @@ -34,7 +34,8 @@ export type DocumentType = | "CONFLUENCE_CONNECTOR" | "CLICKUP_CONNECTOR" | "GOOGLE_CALENDAR_CONNECTOR" - | "GOOGLE_GMAIL_CONNECTOR"; + | "GOOGLE_GMAIL_CONNECTOR" + | "LUMA_CONNECTOR"; export function useDocumentByChunk() { const [document, setDocument] = useState(null); diff --git a/surfsense_web/hooks/use-documents.ts b/surfsense_web/hooks/use-documents.ts index 4c7536689..f3dfa9259 100644 --- a/surfsense_web/hooks/use-documents.ts +++ b/surfsense_web/hooks/use-documents.ts @@ -27,7 +27,8 @@ export type DocumentType = | "CLICKUP_CONNECTOR" | "GOOGLE_CALENDAR_CONNECTOR" | "GOOGLE_GMAIL_CONNECTOR" - | "AIRTABLE_CONNECTOR"; + | "AIRTABLE_CONNECTOR" + | "LUMA_CONNECTOR"; export function useDocuments(searchSpaceId: number, lazy: boolean = false) { const [documents, setDocuments] = useState([]); diff --git a/surfsense_web/hooks/useConnectorEditPage.ts b/surfsense_web/hooks/useConnectorEditPage.ts index f50baa62c..419666151 100644 --- a/surfsense_web/hooks/useConnectorEditPage.ts +++ b/surfsense_web/hooks/useConnectorEditPage.ts @@ -52,6 +52,7 @@ export function useConnectorEditPage(connectorId: number, searchSpaceId: string) JIRA_BASE_URL: "", JIRA_EMAIL: "", JIRA_API_TOKEN: "", + LUMA_API_KEY: "" }, }); @@ -78,6 +79,7 @@ export function useConnectorEditPage(connectorId: number, searchSpaceId: string) JIRA_BASE_URL: config.JIRA_BASE_URL || "", JIRA_EMAIL: config.JIRA_EMAIL || "", JIRA_API_TOKEN: config.JIRA_API_TOKEN || "", + LUMA_API_KEY: config.LUMA_API_KEY || "" }); if (currentConnector.connector_type === "GITHUB_CONNECTOR") { const savedRepos = config.repo_full_names || []; @@ -303,6 +305,16 @@ export function useConnectorEditPage(connectorId: number, searchSpaceId: string) }; } break; + case "LUMA_CONNECTOR": + if (formData.LUMA_API_KEY !== originalConfig.LUMA_API_KEY) { + if (!formData.LUMA_API_KEY) { + toast.error("Luma API Key cannot be empty."); + setIsSaving(false); + return; + } + newConfig = { LUMA_API_KEY: formData.LUMA_API_KEY}; + } + break; } if (newConfig !== null) { @@ -365,6 +377,8 @@ export function useConnectorEditPage(connectorId: number, searchSpaceId: string) editForm.setValue("JIRA_BASE_URL", newlySavedConfig.JIRA_BASE_URL || ""); editForm.setValue("JIRA_EMAIL", newlySavedConfig.JIRA_EMAIL || ""); editForm.setValue("JIRA_API_TOKEN", newlySavedConfig.JIRA_API_TOKEN || ""); + } else if (connector.connector_type == "LUMA_CONNECTOR") { + editForm.setValue("LUMA_API_KEY", newlySavedConfig.LUMA_API_KEY || ""); } } if (connector.connector_type === "GITHUB_CONNECTOR") { diff --git a/surfsense_web/lib/connectors/utils.ts b/surfsense_web/lib/connectors/utils.ts index c1f21657c..64f4b997b 100644 --- a/surfsense_web/lib/connectors/utils.ts +++ b/surfsense_web/lib/connectors/utils.ts @@ -15,6 +15,7 @@ export const getConnectorTypeDisplay = (type: string): string => { GOOGLE_CALENDAR_CONNECTOR: "Google Calendar", GOOGLE_GMAIL_CONNECTOR: "Google Gmail", AIRTABLE_CONNECTOR: "Airtable", + LUMA_CONNECTOR: "Luma" }; return typeMap[type] || type; }; From 94367e42265d251abd1b3e36bc88fa812f080fa8 Mon Sep 17 00:00:00 2001 From: "DESKTOP-RTLN3BA\\$punk" Date: Sun, 28 Sep 2025 22:26:26 -0700 Subject: [PATCH 09/18] chore: linting and formatting --- .../app/agents/researcher/nodes.py | 9 +- .../app/connectors/luma_connector.py | 98 +++++++++++-------- surfsense_backend/app/routes/__init__.py | 2 +- .../app/routes/luma_add_connector_route.py | 72 +++++++------- .../routes/search_source_connectors_routes.py | 6 +- .../app/services/connector_service.py | 9 +- .../app/tasks/connector_indexers/__init__.py | 2 +- .../tasks/connector_indexers/luma_indexer.py | 37 +++---- .../connectors/[connector_id]/page.tsx | 4 +- .../connectors/add/luma-connector/page.tsx | 19 ++-- .../[search_space_id]/connectors/add/page.tsx | 2 +- .../components/editConnector/types.ts | 2 +- surfsense_web/contracts/enums/connector.ts | 2 +- .../contracts/enums/connectorIcons.tsx | 2 +- surfsense_web/hooks/useConnectorEditPage.ts | 6 +- surfsense_web/lib/connectors/utils.ts | 2 +- 16 files changed, 143 insertions(+), 131 deletions(-) diff --git a/surfsense_backend/app/agents/researcher/nodes.py b/surfsense_backend/app/agents/researcher/nodes.py index f036b1d8c..41e47a75d 100644 --- a/surfsense_backend/app/agents/researcher/nodes.py +++ b/surfsense_backend/app/agents/researcher/nodes.py @@ -421,13 +421,14 @@ async def fetch_documents_by_ids( start_time = metadata.get("start_time", "") location_name = metadata.get("location_name", "") meeting_url = metadata.get("meeting_url", "") - + title = f"Luma: {event_name}" if start_time: # Format the start time for display try: if "T" in start_time: from datetime import datetime + start_dt = datetime.fromisoformat( start_time.replace("Z", "+00:00") ) @@ -435,7 +436,7 @@ async def fetch_documents_by_ids( title += f" ({formatted_time})" except Exception: pass - + description = ( doc.content[:100] + "..." if len(doc.content) > 100 @@ -444,8 +445,8 @@ async def fetch_documents_by_ids( if location_name: description += f" | Venue: {location_name}" elif meeting_url: - description += f" | Online Event" - + description += " | Online Event" + url = event_url if event_url else "" elif doc_type == "EXTENSION": diff --git a/surfsense_backend/app/connectors/luma_connector.py b/surfsense_backend/app/connectors/luma_connector.py index 8583bc8a8..62fe3f586 100644 --- a/surfsense_backend/app/connectors/luma_connector.py +++ b/surfsense_backend/app/connectors/luma_connector.py @@ -82,7 +82,9 @@ class LumaConnector: elif response.status_code == 401: raise Exception("Unauthorized: Invalid Luma API key") elif response.status_code == 403: - raise Exception("Forbidden: Access denied or Luma Plus subscription required") + raise Exception( + "Forbidden: Access denied or Luma Plus subscription required" + ) elif response.status_code == 429: raise Exception("Rate limit exceeded: Too many requests") else: @@ -106,7 +108,9 @@ class LumaConnector: except Exception as e: return None, f"Error fetching user info: {e!s}" - def get_all_events(self, limit: int = 100) -> tuple[list[dict[str, Any]], str | None]: + def get_all_events( + self, limit: int = 100 + ) -> tuple[list[dict[str, Any]], str | None]: """ Fetch all events for the authenticated user. @@ -119,32 +123,34 @@ class LumaConnector: try: all_events = [] cursor = None - + while True: params = {"limit": limit} if cursor: params["cursor"] = cursor response = self.make_request("calendar/list-events", params) - + if "entries" not in response: break - + events = response["entries"] all_events.extend(events) - + # Check for pagination - if "next_cursor" in response and response["next_cursor"]: + if response.get("next_cursor"): cursor = response["next_cursor"] else: break - + return all_events, None except Exception as e: return [], f"Error fetching events: {e!s}" - def get_event_details(self, event_id: str) -> tuple[dict[str, Any] | None, str | None]: + def get_event_details( + self, event_id: str + ) -> tuple[dict[str, Any] | None, str | None]: """ Fetch detailed information about a specific event. @@ -160,7 +166,9 @@ class LumaConnector: except Exception as e: return None, f"Error fetching event details for {event_id}: {e!s}" - def get_event_guests(self, event_id: str, limit: int = 100) -> tuple[list[dict[str, Any]], str | None]: + def get_event_guests( + self, event_id: str, limit: int = 100 + ) -> tuple[list[dict[str, Any]], str | None]: """ Fetch guests for a specific event. @@ -174,26 +182,26 @@ class LumaConnector: try: all_guests = [] cursor = None - + while True: params = {"limit": limit} if cursor: params["cursor"] = cursor response = self.make_request(f"events/{event_id}/guests", params) - + if "entries" not in response: break - + guests = response["entries"] all_guests.extend(guests) - + # Check for pagination - if "next_cursor" in response and response["next_cursor"]: + if response.get("next_cursor"): cursor = response["next_cursor"] else: break - + return all_guests, None except Exception as e: @@ -217,7 +225,7 @@ class LumaConnector: # Convert date strings to ISO format for comparison start_dt = datetime.strptime(start_date, "%Y-%m-%d") end_dt = datetime.strptime(end_date, "%Y-%m-%d") - + # Get all events first all_events, error = self.get_all_events() if error: @@ -230,19 +238,23 @@ class LumaConnector: if event_start_time: try: # Parse the event start time (assuming ISO format) - event_dt = datetime.fromisoformat(event_start_time.replace("Z", "+00:00")) + event_dt = datetime.fromisoformat( + event_start_time.replace("Z", "+00:00") + ) event_date = event_dt.date() - + # Check if event falls within the date range if start_dt.date() <= event_date <= end_dt.date(): # Add guest information if requested if include_guests: event_id = event.get("api_id") if event_id: - guests, guest_error = self.get_event_guests(event_id) + guests, guest_error = self.get_event_guests( + event_id + ) if not guest_error: event["guests"] = guests - + filtered_events.append(event) except (ValueError, AttributeError): # Skip events with invalid dates @@ -270,45 +282,45 @@ class LumaConnector: """ # Extract event details event_data = event.get("event", {}) - + title = event_data.get("name", "Untitled Event") description = event_data.get("description", "") event_id = event.get("api_id", "") - + # Extract timing information start_at = event_data.get("start_at", "") end_at = event_data.get("end_at", "") timezone = event_data.get("timezone", "") - + # Format dates start_formatted = self.format_date(start_at) if start_at else "Unknown" end_formatted = self.format_date(end_at) if end_at else "Unknown" - + # Extract location information geo_info = event_data.get("geo_info", {}) location_name = geo_info.get("name", "") address = geo_info.get("address", "") - + # Extract other details url = event_data.get("url", "") visibility = event_data.get("visibility", "") meeting_url = event_data.get("meeting_url", "") - + # Build markdown content markdown_content = f"# {title}\n\n" - + if event_id: markdown_content += f"**Event ID:** {event_id}\n" - + # Add timing information markdown_content += f"**Start:** {start_formatted}\n" markdown_content += f"**End:** {end_formatted}\n" - + if timezone: markdown_content += f"**Timezone:** {timezone}\n" - + markdown_content += "\n" - + # Add location information if location_name or address: markdown_content += "## Location\n\n" @@ -317,45 +329,45 @@ class LumaConnector: if address: markdown_content += f"**Address:** {address}\n" markdown_content += "\n" - + # Add online meeting info if meeting_url: markdown_content += f"**Meeting URL:** {meeting_url}\n\n" - + # Add description if available if description: markdown_content += f"## Description\n\n{description}\n\n" - + # Add event details markdown_content += "## Event Details\n\n" - + if url: markdown_content += f"- **Event URL:** {url}\n" - + if visibility: markdown_content += f"- **Visibility:** {visibility}\n" - + # Add guest information if available if "guests" in event: guests = event["guests"] markdown_content += f"\n## Guests ({len(guests)})\n\n" - + for guest in guests[:10]: # Show first 10 guests guest_data = guest.get("guest", {}) name = guest_data.get("name", "Unknown") email = guest_data.get("email", "") status = guest.get("registration_status", "unknown") - + markdown_content += f"- **{name}**" if email: markdown_content += f" ({email})" markdown_content += f" - Status: {status}\n" - + if len(guests) > 10: markdown_content += f"- ... and {len(guests) - 10} more guests\n" - + markdown_content += "\n" - + return markdown_content @staticmethod diff --git a/surfsense_backend/app/routes/__init__.py b/surfsense_backend/app/routes/__init__.py index d2e160400..1c7e3505f 100644 --- a/surfsense_backend/app/routes/__init__.py +++ b/surfsense_backend/app/routes/__init__.py @@ -4,7 +4,6 @@ from .airtable_add_connector_route import ( router as airtable_add_connector_router, ) from .chats_routes import router as chats_router -from .luma_add_connector_route import router as luma_add_connector_router from .documents_routes import router as documents_router from .google_calendar_add_connector_route import ( router as google_calendar_add_connector_router, @@ -14,6 +13,7 @@ from .google_gmail_add_connector_route import ( ) from .llm_config_routes import router as llm_config_router from .logs_routes import router as logs_router +from .luma_add_connector_route import router as luma_add_connector_router from .podcasts_routes import router as podcasts_router from .search_source_connectors_routes import router as search_source_connectors_router from .search_spaces_routes import router as search_spaces_router diff --git a/surfsense_backend/app/routes/luma_add_connector_route.py b/surfsense_backend/app/routes/luma_add_connector_route.py index 39cc95fc6..37fd0c3df 100644 --- a/surfsense_backend/app/routes/luma_add_connector_route.py +++ b/surfsense_backend/app/routes/luma_add_connector_route.py @@ -1,5 +1,4 @@ import logging -from uuid import UUID from fastapi import APIRouter, Depends, HTTPException from pydantic import BaseModel, Field @@ -22,7 +21,7 @@ router = APIRouter() class AddLumaConnectorRequest(BaseModel): """Request model for adding a Luma connector.""" - + api_key: str = Field(..., description="Luma API key") space_id: int = Field(..., description="Search space ID") @@ -35,15 +34,15 @@ async def add_luma_connector( ): """ Add a new Luma connector for the authenticated user. - + Args: request: The request containing Luma API key and space_id user: Current authenticated user session: Database session - + Returns: Success message and connector details - + Raises: HTTPException: If connector already exists or validation fails """ @@ -52,26 +51,27 @@ async def add_luma_connector( result = await session.execute( select(SearchSourceConnector).filter( SearchSourceConnector.user_id == user.id, - SearchSourceConnector.connector_type == SearchSourceConnectorType.LUMA_CONNECTOR, + SearchSourceConnector.connector_type + == SearchSourceConnectorType.LUMA_CONNECTOR, ) ) existing_connector = result.scalars().first() - + if existing_connector: # Update existing connector with new API key existing_connector.config = {"api_key": request.api_key} existing_connector.is_indexable = True await session.commit() await session.refresh(existing_connector) - + logger.info(f"Updated existing Luma connector for user {user.id}") - + return { "message": "Luma connector updated successfully", "connector_id": existing_connector.id, "connector_type": "LUMA_CONNECTOR", } - + # Create new Luma connector db_connector = SearchSourceConnector( name="Luma Event Connector", @@ -80,21 +80,21 @@ async def add_luma_connector( user_id=user.id, is_indexable=True, ) - + session.add(db_connector) await session.commit() await session.refresh(db_connector) - + logger.info( f"Successfully created Luma connector for user {user.id} with ID {db_connector.id}" ) - + return { "message": "Luma connector added successfully", "connector_id": db_connector.id, "connector_type": "LUMA_CONNECTOR", } - + except IntegrityError as e: await session.rollback() logger.error(f"Database integrity error: {e!s}") @@ -118,14 +118,14 @@ async def delete_luma_connector( ): """ Delete the Luma connector for the authenticated user. - + Args: user: Current authenticated user session: Database session - + Returns: Success message - + Raises: HTTPException: If connector doesn't exist """ @@ -133,24 +133,25 @@ async def delete_luma_connector( result = await session.execute( select(SearchSourceConnector).filter( SearchSourceConnector.user_id == user.id, - SearchSourceConnector.connector_type == SearchSourceConnectorType.LUMA_CONNECTOR, + SearchSourceConnector.connector_type + == SearchSourceConnectorType.LUMA_CONNECTOR, ) ) connector = result.scalars().first() - + if not connector: raise HTTPException( status_code=404, detail="Luma connector not found for this user.", ) - + await session.delete(connector) await session.commit() - + logger.info(f"Successfully deleted Luma connector for user {user.id}") - + return {"message": "Luma connector deleted successfully"} - + except HTTPException: raise except Exception as e: @@ -169,14 +170,14 @@ async def test_luma_connector( ): """ Test the Luma connector for the authenticated user. - + Args: user: Current authenticated user session: Database session - + Returns: Test results including user info and event count - + Raises: HTTPException: If connector doesn't exist or test fails """ @@ -185,20 +186,21 @@ async def test_luma_connector( result = await session.execute( select(SearchSourceConnector).filter( SearchSourceConnector.user_id == user.id, - SearchSourceConnector.connector_type == SearchSourceConnectorType.LUMA_CONNECTOR, + SearchSourceConnector.connector_type + == SearchSourceConnectorType.LUMA_CONNECTOR, ) ) connector = result.scalars().first() - + if not connector: raise HTTPException( status_code=404, detail="Luma connector not found. Please add a connector first.", ) - + # Import LumaConnector from app.connectors.luma_connector import LumaConnector - + # Initialize the connector api_key = connector.config.get("api_key") if not api_key: @@ -206,9 +208,9 @@ async def test_luma_connector( status_code=400, detail="Invalid connector configuration: API key missing.", ) - + luma = LumaConnector(api_key=api_key) - + # Test the connection by fetching user info user_info, error = luma.get_user_info() if error: @@ -216,10 +218,10 @@ async def test_luma_connector( status_code=400, detail=f"Failed to connect to Luma: {error}", ) - + # Try to fetch events events, events_error = luma.get_all_events(limit=10) - + return { "message": "Luma connector is working correctly", "user_info": { @@ -229,7 +231,7 @@ async def test_luma_connector( "event_count": len(events) if not events_error else 0, "events_error": events_error, } - + except HTTPException: raise except Exception as e: diff --git a/surfsense_backend/app/routes/search_source_connectors_routes.py b/surfsense_backend/app/routes/search_source_connectors_routes.py index 19ee43edc..6250e0c07 100644 --- a/surfsense_backend/app/routes/search_source_connectors_routes.py +++ b/surfsense_backend/app/routes/search_source_connectors_routes.py @@ -45,9 +45,9 @@ from app.tasks.connector_indexers import ( index_google_gmail_messages, index_jira_issues, index_linear_issues, + index_luma_events, index_notion_pages, index_slack_messages, - index_luma_events ) from app.users import current_active_user from app.utils.check_ownership import check_ownership @@ -1280,6 +1280,7 @@ async def run_google_gmail_indexing( ) # Optionally update status in DB to indicate failure + # Add new helper functions for luma indexing async def run_luma_indexing_with_new_session( connector_id: int, @@ -1297,6 +1298,7 @@ async def run_luma_indexing_with_new_session( session, connector_id, search_space_id, user_id, start_date, end_date ) + async def run_luma_indexing( session: AsyncSession, connector_id: int, @@ -1338,4 +1340,4 @@ async def run_luma_indexing( f"Luma indexing failed or no documents processed: {error_or_warning}" ) except Exception as e: - logger.error(f"Error in background Luma indexing task: {e!s}") \ No newline at end of file + logger.error(f"Error in background Luma indexing task: {e!s}") diff --git a/surfsense_backend/app/services/connector_service.py b/surfsense_backend/app/services/connector_service.py index de73cc2f5..938500a67 100644 --- a/surfsense_backend/app/services/connector_service.py +++ b/surfsense_backend/app/services/connector_service.py @@ -1951,14 +1951,15 @@ class ConnectorService: info_parts.append(f"Venue: {location_name}") elif location_address: info_parts.append(f"Location: {location_address}") - + if meeting_url: info_parts.append("Online Event") - + if end_time: try: if "T" in end_time: from datetime import datetime + end_dt = datetime.fromisoformat( end_time.replace("Z", "+00:00") ) @@ -1968,10 +1969,10 @@ class ConnectorService: info_parts.append(f"Ends: {end_time}") except Exception: info_parts.append(f"Ends: {end_time}") - + if timezone: info_parts.append(f"TZ: {timezone}") - + if visibility: info_parts.append(f"Visibility: {visibility.title()}") diff --git a/surfsense_backend/app/tasks/connector_indexers/__init__.py b/surfsense_backend/app/tasks/connector_indexers/__init__.py index 92686a31b..fb7936126 100644 --- a/surfsense_backend/app/tasks/connector_indexers/__init__.py +++ b/surfsense_backend/app/tasks/connector_indexers/__init__.py @@ -31,10 +31,10 @@ from .github_indexer import index_github_repos from .google_calendar_indexer import index_google_calendar_events from .google_gmail_indexer import index_google_gmail_messages from .jira_indexer import index_jira_issues -from .luma_indexer import index_luma_events # Issue tracking and project management from .linear_indexer import index_linear_issues +from .luma_indexer import index_luma_events # Documentation and knowledge management from .notion_indexer import index_notion_pages diff --git a/surfsense_backend/app/tasks/connector_indexers/luma_indexer.py b/surfsense_backend/app/tasks/connector_indexers/luma_indexer.py index c53a5f6a0..d7b6d3058 100644 --- a/surfsense_backend/app/tasks/connector_indexers/luma_indexer.py +++ b/surfsense_backend/app/tasks/connector_indexers/luma_indexer.py @@ -84,11 +84,14 @@ async def index_luma_events( "Connector not found", {"error_type": "ConnectorNotFound"}, ) - return 0, f"Connector with ID {connector_id} not found or is not a Luma connector" + return ( + 0, + f"Connector with ID {connector_id} not found or is not a Luma connector", + ) # Get the Luma API key from the connector config api_key = connector.config.get("LUMA_API_KEY") - + if not api_key: await task_logger.log_task_failure( log_entry, @@ -97,7 +100,7 @@ async def index_luma_events( {"error_type": "MissingCredentials"}, ) return 0, "Luma API key not found in connector config" - + logger.info(f"Starting Luma indexing for connector {connector_id}") # Initialize Luma client @@ -107,9 +110,7 @@ async def index_luma_events( {"stage": "client_initialization"}, ) - luma_client = LumaConnector( - api_key=api_key - ) + luma_client = LumaConnector(api_key=api_key) # Calculate date range if start_date is None or end_date is None: @@ -167,9 +168,7 @@ async def index_luma_events( # Get events within date range from Luma try: events, error = luma_client.get_events_by_date_range( - start_date_str, - end_date_str, - include_guests=False + start_date_str, end_date_str, include_guests=False ) if error: @@ -221,7 +220,7 @@ async def index_luma_events( event_id = event.get("api_id") or event_data.get("id") event_name = event_data.get("name", "No Title") event_url = event_data.get("url", "") - + if not event_id: logger.warning(f"Skipping event with missing ID: {event_name}") skipped_events.append(f"{event_name} (missing ID)") @@ -240,19 +239,21 @@ async def index_luma_events( start_at = event_data.get("start_at", "") end_at = event_data.get("end_at", "") timezone = event_data.get("timezone", "") - + # Location info from geo_info geo_info = event_data.get("geo_info", {}) location = geo_info.get("address", "") city = geo_info.get("city", "") - + # Host info hosts = event_data.get("hosts", []) - host_names = ", ".join([host.get("name", "") for host in hosts if host.get("name")]) - + host_names = ", ".join( + [host.get("name", "") for host in hosts if host.get("name")] + ) + description = event_data.get("description", "") cover_url = event_data.get("cover_url", "") - + content_hash = generate_content_hash(event_markdown, search_space_id) # Duplicate check via simple query using helper in base @@ -311,11 +312,11 @@ async def index_luma_events( if len(description) > 300: desc_preview += "..." summary_content += f"Description: {desc_preview}\n" - + summary_embedding = config.embedding_model_instance.embed( summary_content ) - + chunks = await create_document_chunks(event_markdown) document = Document( @@ -397,4 +398,4 @@ async def index_luma_events( {"error_type": type(e).__name__}, ) logger.error(f"Failed to index Luma events: {e!s}", exc_info=True) - return 0, f"Failed to index Luma events: {e!s}" \ No newline at end of file + return 0, f"Failed to index Luma events: {e!s}" diff --git a/surfsense_web/app/dashboard/[search_space_id]/connectors/[connector_id]/page.tsx b/surfsense_web/app/dashboard/[search_space_id]/connectors/[connector_id]/page.tsx index 422908d2e..4152ff786 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/connectors/[connector_id]/page.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/connectors/[connector_id]/page.tsx @@ -53,7 +53,7 @@ const getConnectorTypeDisplay = (type: string): string => { GOOGLE_CALENDAR_CONNECTOR: "Google Calendar Connector", GOOGLE_GMAIL_CONNECTOR: "Google Gmail Connector", AIRTABLE_CONNECTOR: "Airtable Connector", - LUMA_CONNECTOR: "Luma Connector" + LUMA_CONNECTOR: "Luma Connector", // Add other connector types here as needed }; return typeMap[type] || type; @@ -72,7 +72,7 @@ const getApiKeyFieldName = (connectorType: string): string => { GITHUB_CONNECTOR: "GITHUB_PAT", DISCORD_CONNECTOR: "DISCORD_BOT_TOKEN", LINKUP_API: "LINKUP_API_KEY", - LUMA_CONNECTOR: "LUMA_API_KEY" + LUMA_CONNECTOR: "LUMA_API_KEY", }; return fieldMap[connectorType] || ""; }; diff --git a/surfsense_web/app/dashboard/[search_space_id]/connectors/add/luma-connector/page.tsx b/surfsense_web/app/dashboard/[search_space_id]/connectors/add/luma-connector/page.tsx index f3c185f6b..cc5bde60b 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/connectors/add/luma-connector/page.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/connectors/add/luma-connector/page.tsx @@ -54,7 +54,7 @@ export default function LumaConnectorPage() { const searchSpaceId = params.search_space_id as string; const [isSubmitting, setIsSubmitting] = useState(false); const [doesConnectorExist, setDoesConnectorExist] = useState(false); - + const { fetchConnectors, createConnector } = useSearchSourceConnectors(); // Initialize the form @@ -69,8 +69,7 @@ export default function LumaConnectorPage() { useEffect(() => { fetchConnectors().then((data) => { const connector = data.find( - (c: SearchSourceConnector) => - c.connector_type === EnumConnectorName.LUMA_CONNECTOR + (c: SearchSourceConnector) => c.connector_type === EnumConnectorName.LUMA_CONNECTOR ); if (connector) { setDoesConnectorExist(true); @@ -86,14 +85,14 @@ export default function LumaConnectorPage() { name: values.name, connector_type: EnumConnectorName.LUMA_CONNECTOR, config: { - LUMA_API_KEY: values.api_key + LUMA_API_KEY: values.api_key, }, is_indexable: true, last_indexed_at: null, }); toast.success("Luma connector created successfully!"); - + // Navigate back to connectors page router.push(`/dashboard/${searchSpaceId}/connectors`); } catch (error) { @@ -126,9 +125,7 @@ export default function LumaConnectorPage() {

Connect Luma

-

- Connect your Luma account to search events. -

+

Connect your Luma account to search events.

@@ -170,11 +167,7 @@ export default function LumaConnectorPage() { API Key - + Your API key will be encrypted and stored securely. diff --git a/surfsense_web/app/dashboard/[search_space_id]/connectors/add/page.tsx b/surfsense_web/app/dashboard/[search_space_id]/connectors/add/page.tsx index 9887fccb5..d3cee106e 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/connectors/add/page.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/connectors/add/page.tsx @@ -146,7 +146,7 @@ const connectorCategories: ConnectorCategory[] = [ description: "Connect to Luma to search events", icon: getConnectorIcon(EnumConnectorName.LUMA_CONNECTOR, "h-6 w-6"), status: "available", - } + }, ], }, { diff --git a/surfsense_web/components/editConnector/types.ts b/surfsense_web/components/editConnector/types.ts index 5cbf15907..4e91ef305 100644 --- a/surfsense_web/components/editConnector/types.ts +++ b/surfsense_web/components/editConnector/types.ts @@ -43,6 +43,6 @@ export const editConnectorSchema = z.object({ GOOGLE_CALENDAR_CLIENT_SECRET: z.string().optional(), GOOGLE_CALENDAR_REFRESH_TOKEN: z.string().optional(), GOOGLE_CALENDAR_CALENDAR_IDS: z.string().optional(), - LUMA_API_KEY: z.string().optional() + LUMA_API_KEY: z.string().optional(), }); export type EditConnectorFormValues = z.infer; diff --git a/surfsense_web/contracts/enums/connector.ts b/surfsense_web/contracts/enums/connector.ts index bd1277be4..a49314e2e 100644 --- a/surfsense_web/contracts/enums/connector.ts +++ b/surfsense_web/contracts/enums/connector.ts @@ -13,5 +13,5 @@ export enum EnumConnectorName { GOOGLE_CALENDAR_CONNECTOR = "GOOGLE_CALENDAR_CONNECTOR", GOOGLE_GMAIL_CONNECTOR = "GOOGLE_GMAIL_CONNECTOR", AIRTABLE_CONNECTOR = "AIRTABLE_CONNECTOR", - LUMA_CONNECTOR = "LUMA_CONNECTOR" + LUMA_CONNECTOR = "LUMA_CONNECTOR", } diff --git a/surfsense_web/contracts/enums/connectorIcons.tsx b/surfsense_web/contracts/enums/connectorIcons.tsx index c1571c011..f34a10398 100644 --- a/surfsense_web/contracts/enums/connectorIcons.tsx +++ b/surfsense_web/contracts/enums/connectorIcons.tsx @@ -10,10 +10,10 @@ import { IconLayoutKanban, IconLinkPlus, IconMail, + IconSparkles, IconTable, IconTicket, IconWorldWww, - IconSparkles, } from "@tabler/icons-react"; import { File, Globe, Link, Microscope, Search, Sparkles, Telescope, Webhook } from "lucide-react"; import { EnumConnectorName } from "./connector"; diff --git a/surfsense_web/hooks/useConnectorEditPage.ts b/surfsense_web/hooks/useConnectorEditPage.ts index 419666151..ece71dd8f 100644 --- a/surfsense_web/hooks/useConnectorEditPage.ts +++ b/surfsense_web/hooks/useConnectorEditPage.ts @@ -52,7 +52,7 @@ export function useConnectorEditPage(connectorId: number, searchSpaceId: string) JIRA_BASE_URL: "", JIRA_EMAIL: "", JIRA_API_TOKEN: "", - LUMA_API_KEY: "" + LUMA_API_KEY: "", }, }); @@ -79,7 +79,7 @@ export function useConnectorEditPage(connectorId: number, searchSpaceId: string) JIRA_BASE_URL: config.JIRA_BASE_URL || "", JIRA_EMAIL: config.JIRA_EMAIL || "", JIRA_API_TOKEN: config.JIRA_API_TOKEN || "", - LUMA_API_KEY: config.LUMA_API_KEY || "" + LUMA_API_KEY: config.LUMA_API_KEY || "", }); if (currentConnector.connector_type === "GITHUB_CONNECTOR") { const savedRepos = config.repo_full_names || []; @@ -312,7 +312,7 @@ export function useConnectorEditPage(connectorId: number, searchSpaceId: string) setIsSaving(false); return; } - newConfig = { LUMA_API_KEY: formData.LUMA_API_KEY}; + newConfig = { LUMA_API_KEY: formData.LUMA_API_KEY }; } break; } diff --git a/surfsense_web/lib/connectors/utils.ts b/surfsense_web/lib/connectors/utils.ts index 64f4b997b..798204d94 100644 --- a/surfsense_web/lib/connectors/utils.ts +++ b/surfsense_web/lib/connectors/utils.ts @@ -15,7 +15,7 @@ export const getConnectorTypeDisplay = (type: string): string => { GOOGLE_CALENDAR_CONNECTOR: "Google Calendar", GOOGLE_GMAIL_CONNECTOR: "Google Gmail", AIRTABLE_CONNECTOR: "Airtable", - LUMA_CONNECTOR: "Luma" + LUMA_CONNECTOR: "Luma", }; return typeMap[type] || type; }; From a067bbbb4aad70753bb65cb5147661a0473d859b Mon Sep 17 00:00:00 2001 From: "DESKTOP-RTLN3BA\\$punk" Date: Sun, 28 Sep 2025 22:28:55 -0700 Subject: [PATCH 10/18] biome fix --- surfsense_web/hooks/useConnectorEditPage.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/surfsense_web/hooks/useConnectorEditPage.ts b/surfsense_web/hooks/useConnectorEditPage.ts index ece71dd8f..fb5b005a9 100644 --- a/surfsense_web/hooks/useConnectorEditPage.ts +++ b/surfsense_web/hooks/useConnectorEditPage.ts @@ -377,7 +377,7 @@ export function useConnectorEditPage(connectorId: number, searchSpaceId: string) editForm.setValue("JIRA_BASE_URL", newlySavedConfig.JIRA_BASE_URL || ""); editForm.setValue("JIRA_EMAIL", newlySavedConfig.JIRA_EMAIL || ""); editForm.setValue("JIRA_API_TOKEN", newlySavedConfig.JIRA_API_TOKEN || ""); - } else if (connector.connector_type == "LUMA_CONNECTOR") { + } else if (connector.connector_type === "LUMA_CONNECTOR") { editForm.setValue("LUMA_API_KEY", newlySavedConfig.LUMA_API_KEY || ""); } } From 3dc8f7fa15ef472b9304be907df38e93ec8f4314 Mon Sep 17 00:00:00 2001 From: "DESKTOP-RTLN3BA\\$punk" Date: Sun, 28 Sep 2025 22:31:55 -0700 Subject: [PATCH 11/18] biome fix --- surfsense_web/components/dashboard-breadcrumb.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/surfsense_web/components/dashboard-breadcrumb.tsx b/surfsense_web/components/dashboard-breadcrumb.tsx index 9cf022e37..6eb71ef5d 100644 --- a/surfsense_web/components/dashboard-breadcrumb.tsx +++ b/surfsense_web/components/dashboard-breadcrumb.tsx @@ -87,7 +87,7 @@ export function DashboardBreadcrumb() { "tavily-api": "Tavily API", "serper-api": "Serper API", "linkup-api": "LinkUp API", - "luma-connector": "Luma" + "luma-connector": "Luma", }; const connectorLabel = connectorLabels[connectorType] || connectorType; From 1a3faf03d525aea89a79493b7da57a2d56d6b356 Mon Sep 17 00:00:00 2001 From: "DESKTOP-RTLN3BA\\$punk" Date: Tue, 30 Sep 2025 20:27:34 -0700 Subject: [PATCH 12/18] feat: added contact and pricing - Updated framer motion --- surfsense_web/app/contact/page.tsx | 12 + .../[search_space_id]/chats/chats-client.tsx | 2 +- .../connectors/(manage)/page.tsx | 2 +- .../connectors/[connector_id]/edit/page.tsx | 2 +- .../connectors/[connector_id]/page.tsx | 2 +- .../add/airtable-connector/page.tsx | 2 +- .../add/confluence-connector/page.tsx | 2 +- .../connectors/add/discord-connector/page.tsx | 2 +- .../connectors/add/github-connector/page.tsx | 2 +- .../add/google-calendar-connector/page.tsx | 2 +- .../add/google-gmail-connector/page.tsx | 2 +- .../connectors/add/jira-connector/page.tsx | 2 +- .../connectors/add/linear-connector/page.tsx | 2 +- .../connectors/add/linkup-api/page.tsx | 2 +- .../connectors/add/luma-connector/page.tsx | 2 +- .../connectors/add/notion-connector/page.tsx | 2 +- .../[search_space_id]/connectors/add/page.tsx | 2 +- .../connectors/add/serper-api/page.tsx | 2 +- .../connectors/add/slack-connector/page.tsx | 2 +- .../connectors/add/tavily-api/page.tsx | 2 +- .../(manage)/components/DocumentsFilters.tsx | 2 +- .../components/DocumentsTableShell.tsx | 2 +- .../components/PaginationControls.tsx | 2 +- .../documents/(manage)/page.tsx | 2 +- .../documents/upload/page.tsx | 2 +- .../documents/youtube/page.tsx | 2 +- .../[search_space_id]/logs/(manage)/page.tsx | 2 +- .../app/dashboard/api-key/api-key-client.tsx | 2 +- surfsense_web/app/dashboard/page.tsx | 2 +- .../app/dashboard/searchspaces/page.tsx | 2 +- surfsense_web/app/login/GoogleLoginButton.tsx | 2 +- surfsense_web/app/login/LocalLoginForm.tsx | 2 +- surfsense_web/app/login/page.tsx | 2 +- surfsense_web/app/onboard/page.tsx | 2 +- surfsense_web/app/pricing/page.tsx | 12 + surfsense_web/app/register/page.tsx | 2 +- surfsense_web/components/Navbar.tsx | 2 +- .../components/chat/AnimatedEmptyState.tsx | 2 +- .../components/contact/contact-form.tsx | 282 +++++ .../components/onboard/add-provider-step.tsx | 2 +- .../components/onboard/assign-roles-step.tsx | 2 +- .../components/onboard/completion-step.tsx | 2 +- surfsense_web/components/pricing.tsx | 212 ++++ .../components/pricing/pricing-section.tsx | 74 ++ .../components/search-space-form.tsx | 2 +- .../components/settings/llm-role-manager.tsx | 2 +- .../settings/model-config-manager.tsx | 2 +- .../components/theme/theme-toggle.tsx | 2 +- surfsense_web/components/ui/spotlight.tsx | 2 +- surfsense_web/components/ui/switch.tsx | 29 + surfsense_web/components/ui/tilt.tsx | 2 +- surfsense_web/hooks/use-media-query.ts | 39 + surfsense_web/package.json | 7 +- surfsense_web/pnpm-lock.yaml | 123 ++- surfsense_web/public/contact/world.svg | 977 ++++++++++++++++++ 55 files changed, 1802 insertions(+), 55 deletions(-) create mode 100644 surfsense_web/app/contact/page.tsx create mode 100644 surfsense_web/app/pricing/page.tsx create mode 100644 surfsense_web/components/contact/contact-form.tsx create mode 100644 surfsense_web/components/pricing.tsx create mode 100644 surfsense_web/components/pricing/pricing-section.tsx create mode 100644 surfsense_web/components/ui/switch.tsx create mode 100644 surfsense_web/hooks/use-media-query.ts create mode 100644 surfsense_web/public/contact/world.svg diff --git a/surfsense_web/app/contact/page.tsx b/surfsense_web/app/contact/page.tsx new file mode 100644 index 000000000..cb722ff77 --- /dev/null +++ b/surfsense_web/app/contact/page.tsx @@ -0,0 +1,12 @@ +import React from 'react' +import { ContactFormGridWithDetails } from '@/components/contact/contact-form' + +const page = () => { + return ( +
+ +
+ ) +} + +export default page \ No newline at end of file diff --git a/surfsense_web/app/dashboard/[search_space_id]/chats/chats-client.tsx b/surfsense_web/app/dashboard/[search_space_id]/chats/chats-client.tsx index 1222733d0..4177b451a 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/chats/chats-client.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/chats/chats-client.tsx @@ -1,7 +1,7 @@ "use client"; import { format } from "date-fns"; -import { AnimatePresence, motion, type Variants } from "framer-motion"; +import { AnimatePresence, motion, type Variants } from "motion/react"; import { Calendar, CheckCircle, diff --git a/surfsense_web/app/dashboard/[search_space_id]/connectors/(manage)/page.tsx b/surfsense_web/app/dashboard/[search_space_id]/connectors/(manage)/page.tsx index fb803790b..2520307b7 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/connectors/(manage)/page.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/connectors/(manage)/page.tsx @@ -1,7 +1,7 @@ "use client"; import { format } from "date-fns"; -import { motion } from "framer-motion"; +import { motion } from "motion/react"; import { Calendar as CalendarIcon, Edit, Plus, RefreshCw, Trash2 } from "lucide-react"; import { useParams, useRouter } from "next/navigation"; import { useEffect, useState } from "react"; diff --git a/surfsense_web/app/dashboard/[search_space_id]/connectors/[connector_id]/edit/page.tsx b/surfsense_web/app/dashboard/[search_space_id]/connectors/[connector_id]/edit/page.tsx index 83736ff71..b697acdc1 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/connectors/[connector_id]/edit/page.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/connectors/[connector_id]/edit/page.tsx @@ -1,6 +1,6 @@ "use client"; -import { motion } from "framer-motion"; +import { motion } from "motion/react"; import { ArrowLeft, Check, Loader2 } from "lucide-react"; import { useParams, useRouter } from "next/navigation"; import { useEffect } from "react"; diff --git a/surfsense_web/app/dashboard/[search_space_id]/connectors/[connector_id]/page.tsx b/surfsense_web/app/dashboard/[search_space_id]/connectors/[connector_id]/page.tsx index 4152ff786..bf0362464 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/connectors/[connector_id]/page.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/connectors/[connector_id]/page.tsx @@ -1,7 +1,7 @@ "use client"; import { zodResolver } from "@hookform/resolvers/zod"; -import { motion } from "framer-motion"; +import { motion } from "motion/react"; import { ArrowLeft, Check, Info, Loader2 } from "lucide-react"; import { useParams, useRouter } from "next/navigation"; import { useEffect, useState } from "react"; diff --git a/surfsense_web/app/dashboard/[search_space_id]/connectors/add/airtable-connector/page.tsx b/surfsense_web/app/dashboard/[search_space_id]/connectors/add/airtable-connector/page.tsx index b787374ef..4455d0189 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/connectors/add/airtable-connector/page.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/connectors/add/airtable-connector/page.tsx @@ -1,6 +1,6 @@ "use client"; -import { motion } from "framer-motion"; +import { motion } from "motion/react"; import { ArrowLeft, Check, ExternalLink, Loader2 } from "lucide-react"; import Link from "next/link"; import { useParams, useRouter } from "next/navigation"; diff --git a/surfsense_web/app/dashboard/[search_space_id]/connectors/add/confluence-connector/page.tsx b/surfsense_web/app/dashboard/[search_space_id]/connectors/add/confluence-connector/page.tsx index 2eb767bf4..0da10f547 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/connectors/add/confluence-connector/page.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/connectors/add/confluence-connector/page.tsx @@ -1,7 +1,7 @@ "use client"; import { zodResolver } from "@hookform/resolvers/zod"; -import { motion } from "framer-motion"; +import { motion } from "motion/react"; import { ArrowLeft, Check, Info, Loader2 } from "lucide-react"; import { useParams, useRouter } from "next/navigation"; import { useState } from "react"; diff --git a/surfsense_web/app/dashboard/[search_space_id]/connectors/add/discord-connector/page.tsx b/surfsense_web/app/dashboard/[search_space_id]/connectors/add/discord-connector/page.tsx index 12ff1c790..8913c069a 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/connectors/add/discord-connector/page.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/connectors/add/discord-connector/page.tsx @@ -1,7 +1,7 @@ "use client"; import { zodResolver } from "@hookform/resolvers/zod"; -import { motion } from "framer-motion"; +import { motion } from "motion/react"; import { ArrowLeft, Check, Info, Loader2 } from "lucide-react"; import { useParams, useRouter } from "next/navigation"; import { useState } from "react"; diff --git a/surfsense_web/app/dashboard/[search_space_id]/connectors/add/github-connector/page.tsx b/surfsense_web/app/dashboard/[search_space_id]/connectors/add/github-connector/page.tsx index 4fc8e1f41..c0467baa9 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/connectors/add/github-connector/page.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/connectors/add/github-connector/page.tsx @@ -1,7 +1,7 @@ "use client"; import { zodResolver } from "@hookform/resolvers/zod"; -import { motion } from "framer-motion"; +import { motion } from "motion/react"; import { ArrowLeft, Check, CircleAlert, Github, Info, ListChecks, Loader2 } from "lucide-react"; import { useParams, useRouter } from "next/navigation"; import { useState } from "react"; diff --git a/surfsense_web/app/dashboard/[search_space_id]/connectors/add/google-calendar-connector/page.tsx b/surfsense_web/app/dashboard/[search_space_id]/connectors/add/google-calendar-connector/page.tsx index b04a5eddc..6c4dbbf81 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/connectors/add/google-calendar-connector/page.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/connectors/add/google-calendar-connector/page.tsx @@ -1,7 +1,7 @@ "use client"; import { zodResolver } from "@hookform/resolvers/zod"; -import { motion } from "framer-motion"; +import { motion } from "motion/react"; import { ArrowLeft, Check, ExternalLink, Loader2 } from "lucide-react"; import Link from "next/link"; import { useParams, useRouter, useSearchParams } from "next/navigation"; diff --git a/surfsense_web/app/dashboard/[search_space_id]/connectors/add/google-gmail-connector/page.tsx b/surfsense_web/app/dashboard/[search_space_id]/connectors/add/google-gmail-connector/page.tsx index 43123ec4b..b62f564c2 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/connectors/add/google-gmail-connector/page.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/connectors/add/google-gmail-connector/page.tsx @@ -1,7 +1,7 @@ "use client"; import { zodResolver } from "@hookform/resolvers/zod"; -import { motion } from "framer-motion"; +import { motion } from "motion/react"; import { ArrowLeft, Check, ExternalLink, Loader2 } from "lucide-react"; import Link from "next/link"; import { useParams, useRouter, useSearchParams } from "next/navigation"; diff --git a/surfsense_web/app/dashboard/[search_space_id]/connectors/add/jira-connector/page.tsx b/surfsense_web/app/dashboard/[search_space_id]/connectors/add/jira-connector/page.tsx index c7d4466b8..cfb812cfd 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/connectors/add/jira-connector/page.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/connectors/add/jira-connector/page.tsx @@ -1,7 +1,7 @@ "use client"; import { zodResolver } from "@hookform/resolvers/zod"; -import { motion } from "framer-motion"; +import { motion } from "motion/react"; import { ArrowLeft, Check, Info, Loader2 } from "lucide-react"; import { useParams, useRouter } from "next/navigation"; import { useState } from "react"; diff --git a/surfsense_web/app/dashboard/[search_space_id]/connectors/add/linear-connector/page.tsx b/surfsense_web/app/dashboard/[search_space_id]/connectors/add/linear-connector/page.tsx index 9e1581405..602585e5c 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/connectors/add/linear-connector/page.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/connectors/add/linear-connector/page.tsx @@ -1,7 +1,7 @@ "use client"; import { zodResolver } from "@hookform/resolvers/zod"; -import { motion } from "framer-motion"; +import { motion } from "motion/react"; import { ArrowLeft, Check, Info, Loader2 } from "lucide-react"; import { useParams, useRouter } from "next/navigation"; import { useState } from "react"; diff --git a/surfsense_web/app/dashboard/[search_space_id]/connectors/add/linkup-api/page.tsx b/surfsense_web/app/dashboard/[search_space_id]/connectors/add/linkup-api/page.tsx index 582898e0a..b033d2baa 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/connectors/add/linkup-api/page.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/connectors/add/linkup-api/page.tsx @@ -1,7 +1,7 @@ "use client"; import { zodResolver } from "@hookform/resolvers/zod"; -import { motion } from "framer-motion"; +import { motion } from "motion/react"; import { ArrowLeft, Check, Info, Loader2 } from "lucide-react"; import { useParams, useRouter } from "next/navigation"; import { useState } from "react"; diff --git a/surfsense_web/app/dashboard/[search_space_id]/connectors/add/luma-connector/page.tsx b/surfsense_web/app/dashboard/[search_space_id]/connectors/add/luma-connector/page.tsx index cc5bde60b..67af12347 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/connectors/add/luma-connector/page.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/connectors/add/luma-connector/page.tsx @@ -1,7 +1,7 @@ "use client"; import { zodResolver } from "@hookform/resolvers/zod"; -import { motion } from "framer-motion"; +import { motion } from "motion/react"; import { ArrowLeft, Check, Key, Loader2 } from "lucide-react"; import Link from "next/link"; import { useParams, useRouter } from "next/navigation"; diff --git a/surfsense_web/app/dashboard/[search_space_id]/connectors/add/notion-connector/page.tsx b/surfsense_web/app/dashboard/[search_space_id]/connectors/add/notion-connector/page.tsx index dfb66fe9f..6fc383042 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/connectors/add/notion-connector/page.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/connectors/add/notion-connector/page.tsx @@ -1,7 +1,7 @@ "use client"; import { zodResolver } from "@hookform/resolvers/zod"; -import { motion } from "framer-motion"; +import { motion } from "motion/react"; import { ArrowLeft, Check, Info, Loader2 } from "lucide-react"; import { useParams, useRouter } from "next/navigation"; import { useState } from "react"; diff --git a/surfsense_web/app/dashboard/[search_space_id]/connectors/add/page.tsx b/surfsense_web/app/dashboard/[search_space_id]/connectors/add/page.tsx index d3cee106e..a8bb9cfe8 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/connectors/add/page.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/connectors/add/page.tsx @@ -6,7 +6,7 @@ import { IconChevronDown, IconChevronRight, } from "@tabler/icons-react"; -import { AnimatePresence, motion, type Variants } from "framer-motion"; +import { AnimatePresence, motion, type Variants } from "motion/react"; import Link from "next/link"; import { useParams } from "next/navigation"; import { useState } from "react"; diff --git a/surfsense_web/app/dashboard/[search_space_id]/connectors/add/serper-api/page.tsx b/surfsense_web/app/dashboard/[search_space_id]/connectors/add/serper-api/page.tsx index 667126e8e..79afeddeb 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/connectors/add/serper-api/page.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/connectors/add/serper-api/page.tsx @@ -1,7 +1,7 @@ "use client"; import { zodResolver } from "@hookform/resolvers/zod"; -import { motion } from "framer-motion"; +import { motion } from "motion/react"; import { ArrowLeft, Check, Info, Loader2 } from "lucide-react"; import { useParams, useRouter } from "next/navigation"; import { useState } from "react"; diff --git a/surfsense_web/app/dashboard/[search_space_id]/connectors/add/slack-connector/page.tsx b/surfsense_web/app/dashboard/[search_space_id]/connectors/add/slack-connector/page.tsx index 6e9802003..4f5e607e7 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/connectors/add/slack-connector/page.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/connectors/add/slack-connector/page.tsx @@ -1,7 +1,7 @@ "use client"; import { zodResolver } from "@hookform/resolvers/zod"; -import { motion } from "framer-motion"; +import { motion } from "motion/react"; import { ArrowLeft, Check, Info, Loader2 } from "lucide-react"; import { useParams, useRouter } from "next/navigation"; import { useState } from "react"; diff --git a/surfsense_web/app/dashboard/[search_space_id]/connectors/add/tavily-api/page.tsx b/surfsense_web/app/dashboard/[search_space_id]/connectors/add/tavily-api/page.tsx index e2321abcb..d312d46a5 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/connectors/add/tavily-api/page.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/connectors/add/tavily-api/page.tsx @@ -1,7 +1,7 @@ "use client"; import { zodResolver } from "@hookform/resolvers/zod"; -import { motion } from "framer-motion"; +import { motion } from "motion/react"; import { ArrowLeft, Check, Info, Loader2 } from "lucide-react"; import { useParams, useRouter } from "next/navigation"; import { useState } from "react"; diff --git a/surfsense_web/app/dashboard/[search_space_id]/documents/(manage)/components/DocumentsFilters.tsx b/surfsense_web/app/dashboard/[search_space_id]/documents/(manage)/components/DocumentsFilters.tsx index dd8791223..7b3d7beaf 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/documents/(manage)/components/DocumentsFilters.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/documents/(manage)/components/DocumentsFilters.tsx @@ -1,6 +1,6 @@ "use client"; -import { AnimatePresence, motion, type Variants } from "framer-motion"; +import { AnimatePresence, motion, type Variants } from "motion/react"; import { CircleAlert, CircleX, Columns3, Filter, ListFilter, Trash } from "lucide-react"; import React, { useMemo, useRef } from "react"; import { diff --git a/surfsense_web/app/dashboard/[search_space_id]/documents/(manage)/components/DocumentsTableShell.tsx b/surfsense_web/app/dashboard/[search_space_id]/documents/(manage)/components/DocumentsTableShell.tsx index d9f9bb251..1317944b7 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/documents/(manage)/components/DocumentsTableShell.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/documents/(manage)/components/DocumentsTableShell.tsx @@ -1,6 +1,6 @@ "use client"; -import { motion } from "framer-motion"; +import { motion } from "motion/react"; import { ChevronDown, ChevronUp, FileX } from "lucide-react"; import React from "react"; import { DocumentViewer } from "@/components/document-viewer"; diff --git a/surfsense_web/app/dashboard/[search_space_id]/documents/(manage)/components/PaginationControls.tsx b/surfsense_web/app/dashboard/[search_space_id]/documents/(manage)/components/PaginationControls.tsx index 2e6f8f314..33d2f017a 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/documents/(manage)/components/PaginationControls.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/documents/(manage)/components/PaginationControls.tsx @@ -1,6 +1,6 @@ "use client"; -import { motion } from "framer-motion"; +import { motion } from "motion/react"; import { ChevronFirst, ChevronLast, ChevronLeft, ChevronRight } from "lucide-react"; import { Button } from "@/components/ui/button"; import { Label } from "@/components/ui/label"; diff --git a/surfsense_web/app/dashboard/[search_space_id]/documents/(manage)/page.tsx b/surfsense_web/app/dashboard/[search_space_id]/documents/(manage)/page.tsx index 4a69a7533..dfaee38a8 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/documents/(manage)/page.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/documents/(manage)/page.tsx @@ -1,6 +1,6 @@ "use client"; -import { motion } from "framer-motion"; +import { motion } from "motion/react"; import { useParams } from "next/navigation"; import { useEffect, useId, useMemo, useState } from "react"; import { toast } from "sonner"; diff --git a/surfsense_web/app/dashboard/[search_space_id]/documents/upload/page.tsx b/surfsense_web/app/dashboard/[search_space_id]/documents/upload/page.tsx index efea74b49..438e10980 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/documents/upload/page.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/documents/upload/page.tsx @@ -1,6 +1,6 @@ "use client"; -import { AnimatePresence, motion } from "framer-motion"; +import { AnimatePresence, motion } from "motion/react"; import { CheckCircle2, FileType, Info, Tag, Upload, X } from "lucide-react"; import { useParams, useRouter } from "next/navigation"; import { useCallback, useState } from "react"; diff --git a/surfsense_web/app/dashboard/[search_space_id]/documents/youtube/page.tsx b/surfsense_web/app/dashboard/[search_space_id]/documents/youtube/page.tsx index 494420585..af1b702b7 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/documents/youtube/page.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/documents/youtube/page.tsx @@ -2,7 +2,7 @@ import { IconBrandYoutube } from "@tabler/icons-react"; import { type Tag, TagInput } from "emblor"; -import { motion, type Variants } from "framer-motion"; +import { motion, type Variants } from "motion/react"; import { Loader2 } from "lucide-react"; import { useParams, useRouter } from "next/navigation"; import { useState } from "react"; diff --git a/surfsense_web/app/dashboard/[search_space_id]/logs/(manage)/page.tsx b/surfsense_web/app/dashboard/[search_space_id]/logs/(manage)/page.tsx index c1c8fd132..e5c4351bf 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/logs/(manage)/page.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/logs/(manage)/page.tsx @@ -15,7 +15,7 @@ import { useReactTable, type VisibilityState, } from "@tanstack/react-table"; -import { AnimatePresence, motion, type Variants } from "framer-motion"; +import { AnimatePresence, motion, type Variants } from "motion/react"; import { Activity, AlertCircle, diff --git a/surfsense_web/app/dashboard/api-key/api-key-client.tsx b/surfsense_web/app/dashboard/api-key/api-key-client.tsx index 97a36d8e6..97b478dc0 100644 --- a/surfsense_web/app/dashboard/api-key/api-key-client.tsx +++ b/surfsense_web/app/dashboard/api-key/api-key-client.tsx @@ -1,7 +1,7 @@ "use client"; import { IconCheck, IconCopy, IconKey } from "@tabler/icons-react"; -import { AnimatePresence, motion } from "framer-motion"; +import { AnimatePresence, motion } from "motion/react"; import { ArrowLeft } from "lucide-react"; import { useRouter } from "next/navigation"; import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; diff --git a/surfsense_web/app/dashboard/page.tsx b/surfsense_web/app/dashboard/page.tsx index 484b95929..e0ac6b6cf 100644 --- a/surfsense_web/app/dashboard/page.tsx +++ b/surfsense_web/app/dashboard/page.tsx @@ -1,6 +1,6 @@ "use client"; -import { motion, type Variants } from "framer-motion"; +import { motion, type Variants } from "motion/react"; import { AlertCircle, Loader2, Plus, Search, Trash2 } from "lucide-react"; import Image from "next/image"; import Link from "next/link"; diff --git a/surfsense_web/app/dashboard/searchspaces/page.tsx b/surfsense_web/app/dashboard/searchspaces/page.tsx index b1f2f390e..598536c1b 100644 --- a/surfsense_web/app/dashboard/searchspaces/page.tsx +++ b/surfsense_web/app/dashboard/searchspaces/page.tsx @@ -1,6 +1,6 @@ "use client"; -import { motion } from "framer-motion"; +import { motion } from "motion/react"; import { useRouter } from "next/navigation"; import { toast } from "sonner"; import { SearchSpaceForm } from "@/components/search-space-form"; diff --git a/surfsense_web/app/login/GoogleLoginButton.tsx b/surfsense_web/app/login/GoogleLoginButton.tsx index ab0e0ddf8..545f279e7 100644 --- a/surfsense_web/app/login/GoogleLoginButton.tsx +++ b/surfsense_web/app/login/GoogleLoginButton.tsx @@ -1,6 +1,6 @@ "use client"; import { IconBrandGoogleFilled } from "@tabler/icons-react"; -import { motion } from "framer-motion"; +import { motion } from "motion/react"; import { Logo } from "@/components/Logo"; import { AmbientBackground } from "./AmbientBackground"; diff --git a/surfsense_web/app/login/LocalLoginForm.tsx b/surfsense_web/app/login/LocalLoginForm.tsx index b464bea98..a81b9d793 100644 --- a/surfsense_web/app/login/LocalLoginForm.tsx +++ b/surfsense_web/app/login/LocalLoginForm.tsx @@ -1,5 +1,5 @@ "use client"; -import { AnimatePresence, motion } from "framer-motion"; +import { AnimatePresence, motion } from "motion/react"; import Link from "next/link"; import { useRouter } from "next/navigation"; import { useEffect, useState } from "react"; diff --git a/surfsense_web/app/login/page.tsx b/surfsense_web/app/login/page.tsx index c9e9d9090..59ff16ee3 100644 --- a/surfsense_web/app/login/page.tsx +++ b/surfsense_web/app/login/page.tsx @@ -1,6 +1,6 @@ "use client"; -import { AnimatePresence, motion } from "framer-motion"; +import { AnimatePresence, motion } from "motion/react"; import { Loader2 } from "lucide-react"; import { useSearchParams } from "next/navigation"; import { Suspense, useEffect, useState } from "react"; diff --git a/surfsense_web/app/onboard/page.tsx b/surfsense_web/app/onboard/page.tsx index 997b7e4c8..983b240c1 100644 --- a/surfsense_web/app/onboard/page.tsx +++ b/surfsense_web/app/onboard/page.tsx @@ -1,6 +1,6 @@ "use client"; -import { AnimatePresence, motion } from "framer-motion"; +import { AnimatePresence, motion } from "motion/react"; import { ArrowLeft, ArrowRight, Bot, CheckCircle, Sparkles } from "lucide-react"; import { useRouter } from "next/navigation"; import { useEffect, useState } from "react"; diff --git a/surfsense_web/app/pricing/page.tsx b/surfsense_web/app/pricing/page.tsx new file mode 100644 index 000000000..6d71d6ff4 --- /dev/null +++ b/surfsense_web/app/pricing/page.tsx @@ -0,0 +1,12 @@ +import React from 'react' +import PricingBasic from '@/components/pricing/pricing-section' + +const page = () => { + return ( +
+ +
+ ) +} + +export default page \ No newline at end of file diff --git a/surfsense_web/app/register/page.tsx b/surfsense_web/app/register/page.tsx index f046e5cff..303d9a378 100644 --- a/surfsense_web/app/register/page.tsx +++ b/surfsense_web/app/register/page.tsx @@ -1,6 +1,6 @@ "use client"; -import { AnimatePresence, motion } from "framer-motion"; +import { AnimatePresence, motion } from "motion/react"; import Link from "next/link"; import { useRouter } from "next/navigation"; import { useEffect, useState } from "react"; diff --git a/surfsense_web/components/Navbar.tsx b/surfsense_web/components/Navbar.tsx index 8a38ca863..d1df0c927 100644 --- a/surfsense_web/components/Navbar.tsx +++ b/surfsense_web/components/Navbar.tsx @@ -1,6 +1,6 @@ "use client"; import { IconMenu2, IconUser, IconX } from "@tabler/icons-react"; -import { AnimatePresence, motion, useMotionValueEvent, useScroll } from "framer-motion"; +import { AnimatePresence, motion, useMotionValueEvent, useScroll } from "motion/react"; import Link from "next/link"; import { useRef, useState } from "react"; import { cn } from "@/lib/utils"; diff --git a/surfsense_web/components/chat/AnimatedEmptyState.tsx b/surfsense_web/components/chat/AnimatedEmptyState.tsx index 992b75395..1ab8d5c84 100644 --- a/surfsense_web/components/chat/AnimatedEmptyState.tsx +++ b/surfsense_web/components/chat/AnimatedEmptyState.tsx @@ -1,6 +1,6 @@ "use client"; -import { useInView } from "framer-motion"; +import { useInView } from "motion/react"; import { Manrope } from "next/font/google"; import { useEffect, useMemo, useReducer, useRef } from "react"; import { RoughNotation, RoughNotationGroup } from "react-rough-notation"; diff --git a/surfsense_web/components/contact/contact-form.tsx b/surfsense_web/components/contact/contact-form.tsx new file mode 100644 index 000000000..5af9bb783 --- /dev/null +++ b/surfsense_web/components/contact/contact-form.tsx @@ -0,0 +1,282 @@ +"use client"; +import React from "react"; +import { IconMailFilled } from "@tabler/icons-react"; +import { useId } from "react"; +import { cn } from "@/lib/utils"; +import Image from "next/image"; +import { motion } from "motion/react"; +import Link from "next/link"; + +export function ContactFormGridWithDetails() { + return ( +
+
+
+ + + +
+

+ Contact us +

+

+ We are always looking for ways to improve our products and services. + Contact us and let us know how we can help you. +

+ +
+ + rohan@surfsense.com + + + {/*
+

+ +1 (800) 123 XX21 +

*/} +
+ + + https://cal.com/mod-surfsense + +
+
+ + + world map +
+
+
+ +
+ + +
+
+ + +
+
+ + +
+
+ +