diff --git a/surfsense_backend/alembic/versions/96_add_user_id_to_llm_and_image_configs.py b/surfsense_backend/alembic/versions/96_add_user_id_to_llm_and_image_configs.py index 7f600a9e3..06983f510 100644 --- a/surfsense_backend/alembic/versions/96_add_user_id_to_llm_and_image_configs.py +++ b/surfsense_backend/alembic/versions/96_add_user_id_to_llm_and_image_configs.py @@ -141,4 +141,3 @@ def downgrade() -> None: ALTER TABLE image_generation_configs DROP COLUMN IF EXISTS user_id; """ ) - diff --git a/surfsense_backend/app/services/public_chat_service.py b/surfsense_backend/app/services/public_chat_service.py index 9088ed748..ba2dd0079 100644 --- a/surfsense_backend/app/services/public_chat_service.py +++ b/surfsense_backend/app/services/public_chat_service.py @@ -439,7 +439,9 @@ async def list_snapshots_for_search_space( "message_count": len(s.message_ids) if s.message_ids else 0, "thread_id": s.thread_id, "thread_title": thread_titles.get(s.thread_id, "Untitled"), - "created_by_user_id": str(s.created_by_user_id) if s.created_by_user_id else None, + "created_by_user_id": str(s.created_by_user_id) + if s.created_by_user_id + else None, } for s in snapshots ] diff --git a/surfsense_backend/app/tasks/connector_indexers/discord_indexer.py b/surfsense_backend/app/tasks/connector_indexers/discord_indexer.py index 98b798452..8769d03c5 100644 --- a/surfsense_backend/app/tasks/connector_indexers/discord_indexer.py +++ b/surfsense_backend/app/tasks/connector_indexers/discord_indexer.py @@ -651,9 +651,7 @@ async def index_discord_messages( # PHASE 2: Process each batch document one by one # Each document transitions: pending → processing → ready/failed # ======================================================================= - logger.info( - f"Phase 2: Processing {len(batches_to_process)} batch documents" - ) + logger.info(f"Phase 2: Processing {len(batches_to_process)} batch documents") for item in batches_to_process: # Send heartbeat periodically diff --git a/surfsense_backend/app/tasks/connector_indexers/slack_indexer.py b/surfsense_backend/app/tasks/connector_indexers/slack_indexer.py index decdc7b37..01771d2ac 100644 --- a/surfsense_backend/app/tasks/connector_indexers/slack_indexer.py +++ b/surfsense_backend/app/tasks/connector_indexers/slack_indexer.py @@ -357,9 +357,7 @@ async def index_slack_messages( # Group messages into batches of SLACK_BATCH_SIZE # Each batch becomes a single document with conversation context # ======================================================= - for batch_start in range( - 0, len(formatted_messages), SLACK_BATCH_SIZE - ): + for batch_start in range(0, len(formatted_messages), SLACK_BATCH_SIZE): batch = formatted_messages[ batch_start : batch_start + SLACK_BATCH_SIZE ] @@ -377,9 +375,7 @@ async def index_slack_messages( # channel_id + first message ts + last message ts first_msg_ts = batch[0].get("timestamp", "") last_msg_ts = batch[-1].get("timestamp", "") - unique_identifier = ( - f"{channel_id}_{first_msg_ts}_{last_msg_ts}" - ) + unique_identifier = f"{channel_id}_{first_msg_ts}_{last_msg_ts}" unique_identifier_hash = generate_unique_identifier_hash( DocumentType.SLACK_CONNECTOR, unique_identifier, @@ -392,10 +388,8 @@ async def index_slack_messages( ) # Check if document with this unique identifier already exists - existing_document = ( - await check_document_by_unique_identifier( - session, unique_identifier_hash - ) + existing_document = await check_document_by_unique_identifier( + session, unique_identifier_hash ) if existing_document: @@ -405,9 +399,7 @@ async def index_slack_messages( if not DocumentStatus.is_state( existing_document.status, DocumentStatus.READY ): - existing_document.status = ( - DocumentStatus.ready() - ) + existing_document.status = DocumentStatus.ready() documents_skipped += 1 continue @@ -440,10 +432,8 @@ async def index_slack_messages( # Document doesn't exist by unique_identifier_hash # Check if a document with the same content_hash exists (from another connector) with session.no_autoflush: - duplicate_by_content = ( - await check_duplicate_document_by_hash( - session, content_hash - ) + duplicate_by_content = await check_duplicate_document_by_hash( + session, content_hash ) if duplicate_by_content: @@ -496,12 +486,8 @@ async def index_slack_messages( "channel_id": channel_id, "first_message_ts": first_msg_ts, "last_message_ts": last_msg_ts, - "first_message_time": batch[0].get( - "datetime", "Unknown" - ), - "last_message_time": batch[-1].get( - "datetime", "Unknown" - ), + "first_message_time": batch[0].get("datetime", "Unknown"), + "last_message_time": batch[-1].get("datetime", "Unknown"), "message_count": len(batch), "start_date": start_date_str, "end_date": end_date_str, @@ -538,9 +524,7 @@ async def index_slack_messages( # PHASE 2: Process each batch document one by one # Each document transitions: pending → processing → ready/failed # ======================================================================= - logger.info( - f"Phase 2: Processing {len(batches_to_process)} batch documents" - ) + logger.info(f"Phase 2: Processing {len(batches_to_process)} batch documents") for item in batches_to_process: # Send heartbeat periodically @@ -621,9 +605,7 @@ async def index_slack_messages( ) try: await session.commit() - logger.info( - "Successfully committed all Slack document changes to database" - ) + logger.info("Successfully committed all Slack document changes to database") except Exception as e: # Handle any remaining integrity errors gracefully (race conditions, etc.) if ( diff --git a/surfsense_web/app/dashboard/[search_space_id]/documents/(manage)/components/RowActions.tsx b/surfsense_web/app/dashboard/[search_space_id]/documents/(manage)/components/RowActions.tsx index ffb763c6b..137c02f27 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/documents/(manage)/components/RowActions.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/documents/(manage)/components/RowActions.tsx @@ -51,8 +51,7 @@ export function RowActions({ document.status?.state === "pending" || document.status?.state === "processing"; // FILE documents that failed processing cannot be edited - const isFileFailed = - document.document_type === "FILE" && document.status?.state === "failed"; + const isFileFailed = document.document_type === "FILE" && document.status?.state === "failed"; // SURFSENSE_DOCS are system-managed and should not show delete at all const shouldShowDelete = !NON_DELETABLE_DOCUMENT_TYPES.includes( @@ -212,7 +211,8 @@ export function RowActions({ Delete document? - This action cannot be undone. This will permanently delete this document from your search space. + This action cannot be undone. This will permanently delete this document from your + search space. diff --git a/surfsense_web/components/new-chat/model-selector.tsx b/surfsense_web/components/new-chat/model-selector.tsx index d27594ee6..01a926a5a 100644 --- a/surfsense_web/components/new-chat/model-selector.tsx +++ b/surfsense_web/components/new-chat/model-selector.tsx @@ -1,15 +1,7 @@ "use client"; import { useAtomValue } from "jotai"; -import { - Bot, - Check, - ChevronDown, - Edit3, - ImageIcon, - Plus, - Zap, -} from "lucide-react"; +import { Bot, Check, ChevronDown, Edit3, ImageIcon, Plus, Zap } from "lucide-react"; import { useCallback, useMemo, useState } from "react"; import { toast } from "sonner"; import { @@ -77,10 +69,10 @@ export function ModelSelector({ // Image data const { data: imageGlobalConfigs, isLoading: imageGlobalLoading } = useAtomValue(globalImageGenConfigsAtom); - const { data: imageUserConfigs, isLoading: imageUserLoading } = - useAtomValue(imageGenConfigsAtom); + const { data: imageUserConfigs, isLoading: imageUserLoading } = useAtomValue(imageGenConfigsAtom); - const isLoading = llmUserLoading || llmGlobalLoading || prefsLoading || imageGlobalLoading || imageUserLoading; + const isLoading = + llmUserLoading || llmGlobalLoading || prefsLoading || imageGlobalLoading || imageUserLoading; // ─── LLM current config ─── const currentLLMConfig = useMemo(() => { @@ -108,7 +100,9 @@ export function ModelSelector({ }, [preferences, imageGlobalConfigs, imageUserConfigs]); const isImageAutoMode = useMemo(() => { - return currentImageConfig && "is_auto_mode" in currentImageConfig && currentImageConfig.is_auto_mode; + return ( + currentImageConfig && "is_auto_mode" in currentImageConfig && currentImageConfig.is_auto_mode + ); }, [currentImageConfig]); // ─── LLM filtering ─── @@ -244,7 +238,9 @@ export function ModelSelector({ {/* LLM section */} {currentLLMConfig ? ( <> - {getProviderIcon(currentLLMConfig.provider, { isAutoMode: isLLMAutoMode ?? false })} + {getProviderIcon(currentLLMConfig.provider, { + isAutoMode: isLLMAutoMode ?? false, + })} {currentLLMConfig.name} @@ -262,7 +258,9 @@ export function ModelSelector({ {/* Image section */} {currentImageConfig ? ( <> - {getProviderIcon(currentImageConfig.provider, { isAutoMode: isImageAutoMode ?? false })} + {getProviderIcon(currentImageConfig.provider, { + isAutoMode: isImageAutoMode ?? false, + })} {currentImageConfig.name} @@ -373,7 +371,9 @@ export function ModelSelector({ Recommended )} - {isSelected && } + {isSelected && ( + + )}
@@ -436,7 +436,9 @@ export function ModelSelector({
{config.name} - {isSelected && } + {isSelected && ( + + )}
@@ -489,7 +491,10 @@ export function ModelSelector({ {/* ─── Image Tab ─── */} - + {totalImageModels > 3 && (
0 && ( <> - {filteredImageGlobal.length > 0 && } + {filteredImageGlobal.length > 0 && ( + + )}
Your Image Models @@ -591,13 +598,13 @@ export function ModelSelector({ )} >
-
- {getProviderIcon(config.provider)} -
+
{getProviderIcon(config.provider)}
{config.name} - {isSelected && } + {isSelected && ( + + )}
{config.model_name} diff --git a/surfsense_web/components/public-chat-snapshots/public-chat-snapshot-row.tsx b/surfsense_web/components/public-chat-snapshots/public-chat-snapshot-row.tsx index 1e7de9f23..568c52ded 100644 --- a/surfsense_web/components/public-chat-snapshots/public-chat-snapshot-row.tsx +++ b/surfsense_web/components/public-chat-snapshots/public-chat-snapshot-row.tsx @@ -50,9 +50,7 @@ export function PublicChatSnapshotRow({ day: "numeric", }); - const member = snapshot.created_by_user_id - ? memberMap.get(snapshot.created_by_user_id) - : null; + const member = snapshot.created_by_user_id ? memberMap.get(snapshot.created_by_user_id) : null; return ( @@ -77,11 +75,7 @@ export function PublicChatSnapshotRow({ asChild className="h-7 w-7 text-muted-foreground hover:text-foreground" > - + @@ -110,51 +104,49 @@ export function PublicChatSnapshotRow({
- {/* Message count badge */} -
- - - {snapshot.message_count} messages - -
+ {/* Message count badge */} +
+ + + {snapshot.message_count} messages + +
- {/* Public URL – selectable fallback for manual copy */} -
-

- {snapshot.public_url} -

- - - - - - {copied ? "Copied!" : "Copy link"} - - -
+ {/* Public URL – selectable fallback for manual copy */} +
+

+ {snapshot.public_url} +

+ + + + + + {copied ? "Copied!" : "Copy link"} + + +
- {/* Footer: Date + Creator */} + {/* Footer: Date + Creator */}
- - {formattedDate} - + {formattedDate} {member && ( <> · @@ -182,9 +174,7 @@ export function PublicChatSnapshotRow({
- - {member.email || member.name} - + {member.email || member.name} diff --git a/surfsense_web/components/public-chat-snapshots/public-chat-snapshots-manager.tsx b/surfsense_web/components/public-chat-snapshots/public-chat-snapshots-manager.tsx index 5b872404d..24d801409 100644 --- a/surfsense_web/components/public-chat-snapshots/public-chat-snapshots-manager.tsx +++ b/surfsense_web/components/public-chat-snapshots/public-chat-snapshots-manager.tsx @@ -97,13 +97,13 @@ export function PublicChatSnapshotsManager({
- {/* Message count badge */} -
- -
- {/* URL skeleton */} - - {/* Footer: Date + Creator */} + {/* Message count badge */} +
+ +
+ {/* URL skeleton */} + + {/* Footer: Date + Creator */}
diff --git a/surfsense_web/components/settings/image-model-manager.tsx b/surfsense_web/components/settings/image-model-manager.tsx index f2d010e10..ef1a20068 100644 --- a/surfsense_web/components/settings/image-model-manager.tsx +++ b/surfsense_web/components/settings/image-model-manager.tsx @@ -348,16 +348,16 @@ export function ImageModelManager({ searchSpaceId }: ImageModelManagerProps) { {/* Global info */} {globalConfigs.filter((g) => !("is_auto_mode" in g && g.is_auto_mode)).length > 0 && ( - - - - - {globalConfigs.filter((g) => !("is_auto_mode" in g && g.is_auto_mode)).length} global - image model(s) - {" "} - available from your administrator. - - + + + + + {globalConfigs.filter((g) => !("is_auto_mode" in g && g.is_auto_mode)).length} global + image model(s) + {" "} + available from your administrator. + + )} {/* Loading Skeleton */} @@ -417,7 +417,11 @@ export function ImageModelManager({ searchSpaceId }: ImageModelManagerProps) { : "No image models have been added to this space yet. Contact a space owner to add one."}

{canCreate && ( - @@ -457,43 +461,43 @@ export function ImageModelManager({ searchSpaceId }: ImageModelManagerProps) { )}
{(canUpdate || canDelete) && ( -
- {canUpdate && ( - - - - - - Edit - - - )} - {canDelete && ( - - - - - - Delete - - - )} -
- )} +
+ {canUpdate && ( + + + + + + Edit + + + )} + {canDelete && ( + + + + + + Delete + + + )} +
+ )}
{/* Provider + Model */} @@ -507,14 +511,11 @@ export function ImageModelManager({ searchSpaceId }: ImageModelManagerProps) { {/* Footer: Date + Creator */}
- {new Date(config.created_at).toLocaleDateString( - undefined, - { - year: "numeric", - month: "short", - day: "numeric", - } - )} + {new Date(config.created_at).toLocaleDateString(undefined, { + year: "numeric", + month: "short", + day: "numeric", + })} {member && ( <> @@ -574,13 +575,11 @@ export function ImageModelManager({ searchSpaceId }: ImageModelManagerProps) { }} > e.preventDefault()} - > + className="max-w-lg max-h-[90vh] overflow-y-auto" + onOpenAutoFocus={(e) => e.preventDefault()} + > - - {editingConfig ? "Edit Image Model" : "Add Image Model"} - + {editingConfig ? "Edit Image Model" : "Add Image Model"} {editingConfig ? "Update your image generation model" diff --git a/surfsense_web/components/settings/llm-role-manager.tsx b/surfsense_web/components/settings/llm-role-manager.tsx index 6e44b8958..cdc84d400 100644 --- a/surfsense_web/components/settings/llm-role-manager.tsx +++ b/surfsense_web/components/settings/llm-role-manager.tsx @@ -210,8 +210,18 @@ export function LLMRoleManager({ searchSpaceId }: LLMRoleManagerProps) { ...(userImageConfigs ?? []).filter((config) => config.id && config.id.toString().trim() !== ""), ]; - const isLoading = configsLoading || preferencesLoading || globalConfigsLoading || imageConfigsLoading || globalImageConfigsLoading; - const hasError = configsError || preferencesError || globalConfigsError || imageConfigsError || globalImageConfigsError; + const isLoading = + configsLoading || + preferencesLoading || + globalConfigsLoading || + imageConfigsLoading || + globalImageConfigsLoading; + const hasError = + configsError || + preferencesError || + globalConfigsError || + imageConfigsError || + globalImageConfigsError; const hasAnyConfigs = allLLMConfigs.length > 0 || allImageConfigs.length > 0; return ( @@ -253,8 +263,7 @@ export function LLMRoleManager({ searchSpaceId }: LLMRoleManagerProps) { {(configsError?.message ?? "Failed to load LLM configurations") || (preferencesError?.message ?? "Failed to load preferences") || - (globalConfigsError?.message ?? - "Failed to load global configurations")} + (globalConfigsError?.message ?? "Failed to load global configurations")} @@ -305,8 +314,8 @@ export function LLMRoleManager({ searchSpaceId }: LLMRoleManagerProps) { - No configurations found. Please add at least one LLM provider or image model - in the respective settings tabs before assigning roles. + No configurations found. Please add at least one LLM provider or image model in the + respective settings tabs before assigning roles. )} @@ -322,8 +331,7 @@ export function LLMRoleManager({ searchSpaceId }: LLMRoleManagerProps) { {Object.entries(ROLE_DESCRIPTIONS).map(([key, role], index) => { const IconComponent = role.icon; const isImageRole = role.configType === "image"; - const currentAssignment = - assignments[role.prefKey as keyof typeof assignments]; + const currentAssignment = assignments[role.prefKey as keyof typeof assignments]; // Pick the right config lists based on role type const roleGlobalConfigs = isImageRole ? globalImageConfigs : globalConfigs; @@ -332,17 +340,13 @@ export function LLMRoleManager({ searchSpaceId }: LLMRoleManagerProps) { : newLLMConfigs.filter((c) => c.id && c.id.toString().trim() !== ""); const roleAllConfigs = isImageRole ? allImageConfigs : allLLMConfigs; - const assignedConfig = roleAllConfigs.find( - (config) => config.id === currentAssignment - ); + const assignedConfig = roleAllConfigs.find((config) => config.id === currentAssignment); const isAssigned = currentAssignment !== "" && currentAssignment !== null && currentAssignment !== undefined; const isAutoMode = - assignedConfig && - "is_auto_mode" in assignedConfig && - assignedConfig.is_auto_mode; + assignedConfig && "is_auto_mode" in assignedConfig && assignedConfig.is_auto_mode; return ( - +
-

- {role.title} -

+

{role.title}

{role.description}

@@ -389,9 +389,7 @@ export function LLMRoleManager({ searchSpaceId }: LLMRoleManagerProps) {
- {/* Assigned Config Summary */} - {assignedConfig && ( -
+ {/* Assigned Config Summary */} + {assignedConfig && ( +
{isAutoMode ? (
- - {assignedConfig.name} - - {"is_global" in assignedConfig && - assignedConfig.is_global && ( - - 🌐 Global - - )} + {assignedConfig.name} + {"is_global" in assignedConfig && assignedConfig.is_global && ( + + 🌐 Global + + )}
- {getProviderIcon(assignedConfig.provider, { className: "size-3 shrink-0" })} + {getProviderIcon(assignedConfig.provider, { + className: "size-3 shrink-0", + })} {assignedConfig.model_name} @@ -552,9 +538,9 @@ export function LLMRoleManager({ searchSpaceId }: LLMRoleManagerProps) {
)} -
- )} - +
+ )} + ); @@ -572,9 +558,7 @@ export function LLMRoleManager({ searchSpaceId }: LLMRoleManagerProps) { transition={{ duration: 0.2 }} className="flex items-center justify-between gap-3 rounded-lg border border-border bg-muted/50 p-3 md:p-4" > -

- You have unsaved changes -

+

You have unsaved changes

- - Edit - - - )} - {canDelete && ( - - - - - - Delete - - - )} -
- )} +
+ {canUpdate && ( + + + + + + Edit + + + )} + {canDelete && ( + + + + + + Delete + + + )} +
+ )}
{/* Provider + Model */} @@ -453,14 +450,11 @@ export function ModelConfigManager({ searchSpaceId }: ModelConfigManagerProps) { {/* Footer: Date + Creator */}
- {new Date(config.created_at).toLocaleDateString( - undefined, - { - year: "numeric", - month: "short", - day: "numeric", - } - )} + {new Date(config.created_at).toLocaleDateString(undefined, { + year: "numeric", + month: "short", + day: "numeric", + })} {member && ( <> diff --git a/surfsense_web/contracts/types/new-llm-config.types.ts b/surfsense_web/contracts/types/new-llm-config.types.ts index 7b3fca8b0..0885fa7f5 100644 --- a/surfsense_web/contracts/types/new-llm-config.types.ts +++ b/surfsense_web/contracts/types/new-llm-config.types.ts @@ -218,7 +218,9 @@ export const getImageGenConfigsResponse = z.array(imageGenerationConfig); export const updateImageGenConfigRequest = z.object({ id: z.number(), - data: imageGenerationConfig.omit({ id: true, created_at: true, search_space_id: true, user_id: true }).partial(), + data: imageGenerationConfig + .omit({ id: true, created_at: true, search_space_id: true, user_id: true }) + .partial(), }); export const updateImageGenConfigResponse = imageGenerationConfig; diff --git a/surfsense_web/lib/provider-icons.tsx b/surfsense_web/lib/provider-icons.tsx index 4a32f0df0..11cef5bce 100644 --- a/surfsense_web/lib/provider-icons.tsx +++ b/surfsense_web/lib/provider-icons.tsx @@ -1,7 +1,4 @@ -import { - Bot, - Shuffle, -} from "lucide-react"; +import { Bot, Shuffle } from "lucide-react"; import { cn } from "@/lib/utils"; import { Ai21Icon } from "@/components/icons/providers"; import { AnthropicIcon } from "@/components/icons/providers"; @@ -41,10 +38,7 @@ import { ZhipuIcon } from "@/components/icons/providers"; */ export function getProviderIcon( provider: string, - { - isAutoMode, - className = "size-4", - }: { isAutoMode?: boolean; className?: string } = {} + { isAutoMode, className = "size-4" }: { isAutoMode?: boolean; className?: string } = {} ) { if (isAutoMode || provider?.toUpperCase() === "AUTO") { return ; @@ -123,4 +117,3 @@ export function getProviderIcon( return ; } } - diff --git a/surfsense_web/next.config.ts b/surfsense_web/next.config.ts index 55a9296fd..3278b9f3d 100644 --- a/surfsense_web/next.config.ts +++ b/surfsense_web/next.config.ts @@ -41,9 +41,7 @@ const nextConfig: NextConfig = { } // SVGR: import *.svg as React components - const fileLoaderRule = config.module.rules.find( - (rule: any) => rule.test?.test?.(".svg"), - ); + const fileLoaderRule = config.module.rules.find((rule: any) => rule.test?.test?.(".svg")); config.module.rules.push( // Re-apply the existing file loader for *.svg?url imports { @@ -57,7 +55,7 @@ const nextConfig: NextConfig = { issuer: fileLoaderRule.issuer, resourceQuery: { not: [...fileLoaderRule.resourceQuery.not, /url/] }, use: ["@svgr/webpack"], - }, + } ); fileLoaderRule.exclude = /\.svg$/i; diff --git a/surfsense_web/svgr.d.ts b/surfsense_web/svgr.d.ts index 79922fb0f..ada7f47c5 100644 --- a/surfsense_web/svgr.d.ts +++ b/surfsense_web/svgr.d.ts @@ -3,4 +3,3 @@ declare module "*.svg" { const content: FC>; export default content; } -