diff --git a/surfsense_backend/alembic/versions/60_add_surfsense_docs_tables.py b/surfsense_backend/alembic/versions/60_add_surfsense_docs_tables.py index 7e5aa9437..ed03a4077 100644 --- a/surfsense_backend/alembic/versions/60_add_surfsense_docs_tables.py +++ b/surfsense_backend/alembic/versions/60_add_surfsense_docs_tables.py @@ -7,7 +7,6 @@ Revises: 59 from collections.abc import Sequence from alembic import op - from app.config import config # revision identifiers, used by Alembic. @@ -22,7 +21,7 @@ EMBEDDING_DIM = config.embedding_model_instance.dimension def upgrade() -> None: """Create surfsense_docs_documents and surfsense_docs_chunks tables.""" - + # Create surfsense_docs_documents table op.execute( f""" @@ -46,7 +45,7 @@ def upgrade() -> None: END$$; """ ) - + # Create indexes for surfsense_docs_documents op.execute( """ @@ -75,7 +74,7 @@ def upgrade() -> None: END$$; """ ) - + # Create surfsense_docs_chunks table op.execute( f""" @@ -96,7 +95,7 @@ def upgrade() -> None: END$$; """ ) - + # Create indexes for surfsense_docs_chunks op.execute( """ @@ -111,7 +110,7 @@ def upgrade() -> None: END$$; """ ) - + # Create vector indexes for similarity search op.execute( """ @@ -119,14 +118,14 @@ def upgrade() -> None: ON surfsense_docs_documents USING hnsw (embedding public.vector_cosine_ops); """ ) - + op.execute( """ CREATE INDEX IF NOT EXISTS surfsense_docs_chunks_vector_index ON surfsense_docs_chunks USING hnsw (embedding public.vector_cosine_ops); """ ) - + # Create full-text search indexes (same pattern as documents/chunks tables) op.execute( """ @@ -134,7 +133,7 @@ def upgrade() -> None: ON surfsense_docs_documents USING gin (to_tsvector('english', content)); """ ) - + op.execute( """ CREATE INDEX IF NOT EXISTS surfsense_docs_chunks_search_index @@ -148,18 +147,17 @@ def downgrade() -> None: # Drop full-text search indexes op.execute("DROP INDEX IF EXISTS surfsense_docs_chunks_search_index") op.execute("DROP INDEX IF EXISTS surfsense_docs_documents_search_index") - + # Drop vector indexes op.execute("DROP INDEX IF EXISTS surfsense_docs_chunks_vector_index") op.execute("DROP INDEX IF EXISTS surfsense_docs_documents_vector_index") - + # Drop regular indexes op.execute("DROP INDEX IF EXISTS ix_surfsense_docs_chunks_document_id") op.execute("DROP INDEX IF EXISTS ix_surfsense_docs_documents_updated_at") op.execute("DROP INDEX IF EXISTS ix_surfsense_docs_documents_content_hash") op.execute("DROP INDEX IF EXISTS ix_surfsense_docs_documents_source") - + # Drop tables (chunks first due to FK) op.execute("DROP TABLE IF EXISTS surfsense_docs_chunks") op.execute("DROP TABLE IF EXISTS surfsense_docs_documents") - diff --git a/surfsense_backend/app/agents/new_chat/tools/search_surfsense_docs.py b/surfsense_backend/app/agents/new_chat/tools/search_surfsense_docs.py index a34e16ff2..b9b370c23 100644 --- a/surfsense_backend/app/agents/new_chat/tools/search_surfsense_docs.py +++ b/surfsense_backend/app/agents/new_chat/tools/search_surfsense_docs.py @@ -48,10 +48,12 @@ def format_surfsense_docs_results(results: list[tuple]) -> str: "metadata": {"source": doc.source}, "chunks": [], } - grouped[doc.id]["chunks"].append({ - "chunk_id": f"doc-{chunk.id}", - "content": chunk.content, - }) + grouped[doc.id]["chunks"].append( + { + "chunk_id": f"doc-{chunk.id}", + "content": chunk.content, + } + ) # Render XML matching format_documents_for_context structure parts: list[str] = [] @@ -70,7 +72,9 @@ def format_surfsense_docs_results(results: list[tuple]) -> str: parts.append("") for ch in g["chunks"]: - parts.append(f" ") + parts.append( + f" " + ) parts.append("") parts.append("") @@ -157,4 +161,3 @@ def create_search_surfsense_docs_tool(db_session: AsyncSession): ) return search_surfsense_docs - diff --git a/surfsense_backend/app/db.py b/surfsense_backend/app/db.py index 006d73358..a0b174bf6 100644 --- a/surfsense_backend/app/db.py +++ b/surfsense_backend/app/db.py @@ -436,7 +436,9 @@ class SurfsenseDocsDocument(BaseModel, TimestampMixin): __tablename__ = "surfsense_docs_documents" - source = Column(String, nullable=False, unique=True, index=True) # File path: "connectors/slack.mdx" + source = Column( + String, nullable=False, unique=True, index=True + ) # File path: "connectors/slack.mdx" title = Column(String, nullable=False) content = Column(Text, nullable=False) content_hash = Column(String, nullable=False, index=True) # For detecting changes diff --git a/surfsense_backend/app/routes/search_source_connectors_routes.py b/surfsense_backend/app/routes/search_source_connectors_routes.py index 06d75c7c9..8e8ebb72d 100644 --- a/surfsense_backend/app/routes/search_source_connectors_routes.py +++ b/surfsense_backend/app/routes/search_source_connectors_routes.py @@ -623,10 +623,7 @@ async def index_connector_content( SearchSourceConnectorType.LUMA_CONNECTOR, ]: # Default to today if no end_date provided (users can manually select future dates) - if end_date is None: - indexing_to = today_str - else: - indexing_to = end_date + indexing_to = today_str if end_date is None else end_date else: # For non-calendar connectors, cap at today indexing_to = end_date if end_date else today_str diff --git a/surfsense_backend/app/schemas/surfsense_docs.py b/surfsense_backend/app/schemas/surfsense_docs.py index 7464df342..c6029320f 100644 --- a/surfsense_backend/app/schemas/surfsense_docs.py +++ b/surfsense_backend/app/schemas/surfsense_docs.py @@ -24,4 +24,3 @@ class SurfsenseDocsDocumentWithChunksRead(BaseModel): chunks: list[SurfsenseDocsChunkRead] model_config = ConfigDict(from_attributes=True) - diff --git a/surfsense_backend/app/tasks/surfsense_docs_indexer.py b/surfsense_backend/app/tasks/surfsense_docs_indexer.py index f2c1e69ba..ef287bc65 100644 --- a/surfsense_backend/app/tasks/surfsense_docs_indexer.py +++ b/surfsense_backend/app/tasks/surfsense_docs_indexer.py @@ -19,7 +19,12 @@ from app.db import SurfsenseDocsChunk, SurfsenseDocsDocument, async_session_make logger = logging.getLogger(__name__) # Path to docs relative to project root -DOCS_DIR = Path(__file__).resolve().parent.parent.parent.parent / "surfsense_web" / "content" / "docs" +DOCS_DIR = ( + Path(__file__).resolve().parent.parent.parent.parent + / "surfsense_web" + / "content" + / "docs" +) def parse_mdx_frontmatter(content: str) -> tuple[str, str]: @@ -38,7 +43,7 @@ def parse_mdx_frontmatter(content: str) -> tuple[str, str]: if match: frontmatter = match.group(1) - content_without_frontmatter = content[match.end():] + content_without_frontmatter = content[match.end() :] # Extract title from frontmatter title_match = re.search(r"^title:\s*(.+)$", frontmatter, re.MULTILINE) @@ -93,10 +98,10 @@ def create_surfsense_docs_chunks(content: str) -> list[SurfsenseDocsChunk]: async def index_surfsense_docs(session: AsyncSession) -> tuple[int, int, int, int]: """ Index all Surfsense documentation files. - + Args: session: SQLAlchemy async session - + Returns: Tuple of (created, updated, skipped, deleted) counts """ @@ -104,45 +109,47 @@ async def index_surfsense_docs(session: AsyncSession) -> tuple[int, int, int, in updated = 0 skipped = 0 deleted = 0 - + # Get all existing docs from database existing_docs_result = await session.execute( - select(SurfsenseDocsDocument).options(selectinload(SurfsenseDocsDocument.chunks)) + select(SurfsenseDocsDocument).options( + selectinload(SurfsenseDocsDocument.chunks) + ) ) existing_docs = {doc.source: doc for doc in existing_docs_result.scalars().all()} - + # Track which sources we've processed processed_sources = set() - + # Get all MDX files mdx_files = get_all_mdx_files() logger.info(f"Found {len(mdx_files)} MDX files to index") - + for mdx_file in mdx_files: try: source = str(mdx_file.relative_to(DOCS_DIR)) processed_sources.add(source) - + # Read file content raw_content = mdx_file.read_text(encoding="utf-8") title, content = parse_mdx_frontmatter(raw_content) content_hash = generate_surfsense_docs_content_hash(raw_content) - + if source in existing_docs: existing_doc = existing_docs[source] - + # Check if content changed if existing_doc.content_hash == content_hash: logger.debug(f"Skipping unchanged: {source}") skipped += 1 continue - + # Content changed - update document logger.info(f"Updating changed document: {source}") - + # Create new chunks chunks = create_surfsense_docs_chunks(content) - + # Update document fields existing_doc.title = title existing_doc.content = content @@ -150,14 +157,14 @@ async def index_surfsense_docs(session: AsyncSession) -> tuple[int, int, int, in existing_doc.embedding = config.embedding_model_instance.embed(content) existing_doc.chunks = chunks existing_doc.updated_at = datetime.now(UTC) - + updated += 1 else: # New document - create it logger.info(f"Creating new document: {source}") - + chunks = create_surfsense_docs_chunks(content) - + document = SurfsenseDocsDocument( source=source, title=title, @@ -167,56 +174,56 @@ async def index_surfsense_docs(session: AsyncSession) -> tuple[int, int, int, in chunks=chunks, updated_at=datetime.now(UTC), ) - + session.add(document) created += 1 - + except Exception as e: logger.error(f"Error processing {mdx_file}: {e}", exc_info=True) continue - + # Delete documents for removed files for source, doc in existing_docs.items(): if source not in processed_sources: logger.info(f"Deleting removed document: {source}") await session.delete(doc) deleted += 1 - + # Commit all changes await session.commit() - + logger.info( f"Indexing complete: {created} created, {updated} updated, " f"{skipped} skipped, {deleted} deleted" ) - + return created, updated, skipped, deleted async def seed_surfsense_docs() -> tuple[int, int, int, int]: """ Seed Surfsense documentation into the database. - + This function indexes all MDX files from the docs directory. It handles creating, updating, and deleting docs based on content changes. - + Returns: Tuple of (created, updated, skipped, deleted) counts Returns (0, 0, 0, 0) if an error occurs """ logger.info("Starting Surfsense docs indexing...") - + try: async with async_session_maker() as session: created, updated, skipped, deleted = await index_surfsense_docs(session) - + logger.info( f"Surfsense docs indexing complete: " f"created={created}, updated={updated}, skipped={skipped}, deleted={deleted}" ) - + return created, updated, skipped, deleted - + except Exception as e: logger.error(f"Failed to seed Surfsense docs: {e}", exc_info=True) return 0, 0, 0, 0 diff --git a/surfsense_backend/scripts/seed_surfsense_docs.py b/surfsense_backend/scripts/seed_surfsense_docs.py index d9536bf91..68899c2aa 100644 --- a/surfsense_backend/scripts/seed_surfsense_docs.py +++ b/surfsense_backend/scripts/seed_surfsense_docs.py @@ -24,9 +24,9 @@ def main(): print("=" * 50) print(" Surfsense Documentation Seeding") print("=" * 50) - + created, updated, skipped, deleted = asyncio.run(seed_surfsense_docs()) - + print() print("Results:") print(f" Created: {created}") diff --git a/surfsense_web/app/dashboard/page.tsx b/surfsense_web/app/dashboard/page.tsx index 3e6d71829..767ce5201 100644 --- a/surfsense_web/app/dashboard/page.tsx +++ b/surfsense_web/app/dashboard/page.tsx @@ -105,9 +105,7 @@ function EmptyState({ onCreateClick }: { onCreateClick: () => void }) {

{t("welcome_title")}

-

- {t("welcome_description")} -

+

{t("welcome_description")}

- - {copied ? t("copied") : t("copy")} - + {copied ? t("copied") : t("copy")} diff --git a/surfsense_web/components/assistant-ui/connector-popup/components/connector-status-badge.tsx b/surfsense_web/components/assistant-ui/connector-popup/components/connector-status-badge.tsx index ecc3a11cd..7412a4148 100644 --- a/surfsense_web/components/assistant-ui/connector-popup/components/connector-status-badge.tsx +++ b/surfsense_web/components/assistant-ui/connector-popup/components/connector-status-badge.tsx @@ -3,8 +3,8 @@ import { AlertTriangle, Ban, Wrench } from "lucide-react"; import type { FC } from "react"; import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"; -import type { ConnectorStatus } from "../config/connector-status-config"; import { cn } from "@/lib/utils"; +import type { ConnectorStatus } from "../config/connector-status-config"; interface ConnectorStatusBadgeProps { status: ConnectorStatus; diff --git a/surfsense_web/components/assistant-ui/connector-popup/views/connector-accounts-list-view.tsx b/surfsense_web/components/assistant-ui/connector-popup/views/connector-accounts-list-view.tsx index 74dd51929..bec4bfcb8 100644 --- a/surfsense_web/components/assistant-ui/connector-popup/views/connector-accounts-list-view.tsx +++ b/surfsense_web/components/assistant-ui/connector-popup/views/connector-accounts-list-view.tsx @@ -8,8 +8,8 @@ import { getConnectorIcon } from "@/contracts/enums/connectorIcons"; import type { SearchSourceConnector } from "@/contracts/types/connector.types"; import type { LogActiveTask, LogSummary } from "@/contracts/types/log.types"; import { cn } from "@/lib/utils"; -import { getConnectorDisplayName } from "../tabs/all-connectors-tab"; import { useConnectorStatus } from "../hooks/use-connector-status"; +import { getConnectorDisplayName } from "../tabs/all-connectors-tab"; interface ConnectorAccountsListViewProps { connectorType: string; diff --git a/surfsense_web/components/assistant-ui/inline-citation.tsx b/surfsense_web/components/assistant-ui/inline-citation.tsx index 9eab9a3c3..6b5a4b091 100644 --- a/surfsense_web/components/assistant-ui/inline-citation.tsx +++ b/surfsense_web/components/assistant-ui/inline-citation.tsx @@ -15,7 +15,11 @@ interface InlineCitationProps { * Renders a clickable numbered badge that opens the SourceDetailPanel with document chunk details. * Supports both regular knowledge base chunks and Surfsense documentation chunks. */ -export const InlineCitation: FC = ({ chunkId, citationNumber, isDocsChunk = false }) => { +export const InlineCitation: FC = ({ + chunkId, + citationNumber, + isDocsChunk = false, +}) => { const [isOpen, setIsOpen] = useState(false); return ( diff --git a/surfsense_web/components/layout/index.ts b/surfsense_web/components/layout/index.ts index b9c271915..18f8cc9d3 100644 --- a/surfsense_web/components/layout/index.ts +++ b/surfsense_web/components/layout/index.ts @@ -6,9 +6,9 @@ export type { NavItem, NoteItem, PageUsage, + SearchSpace, SidebarSectionProps, User, - SearchSpace, } from "./types/layout.types"; export { AllSearchSpacesSheet, @@ -23,10 +23,10 @@ export { NavSection, NoteListItem, PageUsageDisplay, + SearchSpaceAvatar, Sidebar, SidebarCollapseButton, SidebarHeader, SidebarSection, SidebarUserProfile, - SearchSpaceAvatar, } from "./ui"; diff --git a/surfsense_web/components/layout/providers/LayoutDataProvider.tsx b/surfsense_web/components/layout/providers/LayoutDataProvider.tsx index 8f42e22aa..70bc96f58 100644 --- a/surfsense_web/components/layout/providers/LayoutDataProvider.tsx +++ b/surfsense_web/components/layout/providers/LayoutDataProvider.tsx @@ -28,8 +28,8 @@ import { resetUser, trackLogout } from "@/lib/posthog/events"; import { cacheKeys } from "@/lib/query-client/cache-keys"; import type { ChatItem, NavItem, NoteItem, SearchSpace } from "../types/layout.types"; import { CreateSearchSpaceDialog } from "../ui/dialogs"; -import { LayoutShell } from "../ui/shell"; import { AllSearchSpacesSheet } from "../ui/sheets"; +import { LayoutShell } from "../ui/shell"; import { AllChatsSidebar } from "../ui/sidebar/AllChatsSidebar"; import { AllNotesSidebar } from "../ui/sidebar/AllNotesSidebar"; diff --git a/surfsense_web/components/layout/ui/dialogs/CreateSearchSpaceDialog.tsx b/surfsense_web/components/layout/ui/dialogs/CreateSearchSpaceDialog.tsx index 978d46f6c..7e962536f 100644 --- a/surfsense_web/components/layout/ui/dialogs/CreateSearchSpaceDialog.tsx +++ b/surfsense_web/components/layout/ui/dialogs/CreateSearchSpaceDialog.tsx @@ -104,11 +104,7 @@ export function CreateSearchSpaceDialog({ open, onOpenChange }: CreateSearchSpac {t("name_label")} - + @@ -163,4 +159,3 @@ export function CreateSearchSpaceDialog({ open, onOpenChange }: CreateSearchSpac ); } - diff --git a/surfsense_web/components/layout/ui/dialogs/index.ts b/surfsense_web/components/layout/ui/dialogs/index.ts index 28f3b387d..807a227de 100644 --- a/surfsense_web/components/layout/ui/dialogs/index.ts +++ b/surfsense_web/components/layout/ui/dialogs/index.ts @@ -1,2 +1 @@ export { CreateSearchSpaceDialog } from "./CreateSearchSpaceDialog"; - diff --git a/surfsense_web/components/layout/ui/icon-rail/SearchSpaceAvatar.tsx b/surfsense_web/components/layout/ui/icon-rail/SearchSpaceAvatar.tsx index 397076cb6..77f4de899 100644 --- a/surfsense_web/components/layout/ui/icon-rail/SearchSpaceAvatar.tsx +++ b/surfsense_web/components/layout/ui/icon-rail/SearchSpaceAvatar.tsx @@ -42,7 +42,12 @@ function getInitials(name: string): string { return name.slice(0, 2).toUpperCase(); } -export function SearchSpaceAvatar({ name, isActive, onClick, size = "md" }: SearchSpaceAvatarProps) { +export function SearchSpaceAvatar({ + name, + isActive, + onClick, + size = "md", +}: SearchSpaceAvatarProps) { const bgColor = stringToColor(name); const initials = getInitials(name); const sizeClasses = size === "sm" ? "h-8 w-8 text-xs" : "h-10 w-10 text-sm"; diff --git a/surfsense_web/components/layout/ui/index.ts b/surfsense_web/components/layout/ui/index.ts index c5aba9250..bd3d54838 100644 --- a/surfsense_web/components/layout/ui/index.ts +++ b/surfsense_web/components/layout/ui/index.ts @@ -1,8 +1,8 @@ export { CreateSearchSpaceDialog } from "./dialogs"; export { Header } from "./header"; export { IconRail, NavIcon, SearchSpaceAvatar } from "./icon-rail"; -export { LayoutShell } from "./shell"; export { AllSearchSpacesSheet } from "./sheets"; +export { LayoutShell } from "./shell"; export { ChatListItem, MobileSidebar, diff --git a/surfsense_web/components/layout/ui/sheets/AllSearchSpacesSheet.tsx b/surfsense_web/components/layout/ui/sheets/AllSearchSpacesSheet.tsx index d144c79b3..401de41c3 100644 --- a/surfsense_web/components/layout/ui/sheets/AllSearchSpacesSheet.tsx +++ b/surfsense_web/components/layout/ui/sheets/AllSearchSpacesSheet.tsx @@ -1,6 +1,15 @@ "use client"; -import { Calendar, MoreHorizontal, Search, Settings, Share2, Trash2, UserCheck, Users } from "lucide-react"; +import { + Calendar, + MoreHorizontal, + Search, + Settings, + Share2, + Trash2, + UserCheck, + Users, +} from "lucide-react"; import { useTranslations } from "next-intl"; import { useState } from "react"; import { @@ -112,9 +121,7 @@ export function AllSearchSpacesSheet({

{t("no_search_spaces")}

-

- {t("create_first_search_space")} -

+

{t("create_first_search_space")}

{onCreateNew && ( - - - handleSettings(e, space)}> - - {tCommon("settings")} - - - handleDeleteClick(e, space)} - className="text-destructive focus:text-destructive" - > - - {tCommon("delete")} - - - - )} - + {space.isOwner && ( + + + + + + handleSettings(e, space)}> + + {tCommon("settings")} + + + handleDeleteClick(e, space)} + className="text-destructive focus:text-destructive" + > + + {tCommon("delete")} + + + + )} +
diff --git a/surfsense_web/components/layout/ui/sheets/index.ts b/surfsense_web/components/layout/ui/sheets/index.ts index b2d05f1a8..d3db749bb 100644 --- a/surfsense_web/components/layout/ui/sheets/index.ts +++ b/surfsense_web/components/layout/ui/sheets/index.ts @@ -1,2 +1 @@ export { AllSearchSpacesSheet } from "./AllSearchSpacesSheet"; - diff --git a/surfsense_web/components/layout/ui/shell/LayoutShell.tsx b/surfsense_web/components/layout/ui/shell/LayoutShell.tsx index ee2978113..1bb0a015a 100644 --- a/surfsense_web/components/layout/ui/shell/LayoutShell.tsx +++ b/surfsense_web/components/layout/ui/shell/LayoutShell.tsx @@ -10,8 +10,8 @@ import type { NavItem, NoteItem, PageUsage, - User, SearchSpace, + User, } from "../../types/layout.types"; import { Header } from "../header"; import { IconRail } from "../icon-rail"; diff --git a/surfsense_web/components/layout/ui/sidebar/MobileSidebar.tsx b/surfsense_web/components/layout/ui/sidebar/MobileSidebar.tsx index 88b22158f..c1874bfd1 100644 --- a/surfsense_web/components/layout/ui/sidebar/MobileSidebar.tsx +++ b/surfsense_web/components/layout/ui/sidebar/MobileSidebar.tsx @@ -9,8 +9,8 @@ import type { NavItem, NoteItem, PageUsage, - User, SearchSpace, + User, } from "../../types/layout.types"; import { IconRail } from "../icon-rail"; import { Sidebar } from "./Sidebar"; diff --git a/surfsense_web/components/layout/ui/sidebar/Sidebar.tsx b/surfsense_web/components/layout/ui/sidebar/Sidebar.tsx index 69dcb7391..0fdec2a03 100644 --- a/surfsense_web/components/layout/ui/sidebar/Sidebar.tsx +++ b/surfsense_web/components/layout/ui/sidebar/Sidebar.tsx @@ -11,8 +11,8 @@ import type { NavItem, NoteItem, PageUsage, - User, SearchSpace, + User, } from "../../types/layout.types"; import { ChatListItem } from "./ChatListItem"; import { NavSection } from "./NavSection"; @@ -289,7 +289,12 @@ export function Sidebar({ )} - +
); diff --git a/surfsense_web/components/layout/ui/sidebar/SidebarHeader.tsx b/surfsense_web/components/layout/ui/sidebar/SidebarHeader.tsx index 9373a6169..4ed5e9d34 100644 --- a/surfsense_web/components/layout/ui/sidebar/SidebarHeader.tsx +++ b/surfsense_web/components/layout/ui/sidebar/SidebarHeader.tsx @@ -43,7 +43,9 @@ export function SidebarHeader({ isCollapsed ? "w-10" : "w-50" )} > - {searchSpace?.name ?? t("select_search_space")} + + {searchSpace?.name ?? t("select_search_space")} + diff --git a/surfsense_web/lib/apis/base-api.service.ts b/surfsense_web/lib/apis/base-api.service.ts index d7c281ac6..5849003e2 100644 --- a/surfsense_web/lib/apis/base-api.service.ts +++ b/surfsense_web/lib/apis/base-api.service.ts @@ -161,52 +161,52 @@ class BaseApiService { } } - // biome-ignore lint/suspicious: Unknown - let data; - const responseType = mergedOptions.responseType; + // biome-ignore lint/suspicious: Unknown + let data; + const responseType = mergedOptions.responseType; - try { - switch (responseType) { - case ResponseType.JSON: - data = await response.json(); - break; - case ResponseType.TEXT: - data = await response.text(); - break; - case ResponseType.BLOB: - data = await response.blob(); - break; - case ResponseType.ARRAY_BUFFER: - data = await response.arrayBuffer(); - break; - // Add more cases as needed - default: - data = await response.json(); + try { + switch (responseType) { + case ResponseType.JSON: + data = await response.json(); + break; + case ResponseType.TEXT: + data = await response.text(); + break; + case ResponseType.BLOB: + data = await response.blob(); + break; + case ResponseType.ARRAY_BUFFER: + data = await response.arrayBuffer(); + break; + // Add more cases as needed + default: + data = await response.json(); + } + } catch (error) { + console.error("Failed to parse response as JSON:", error); + throw new AppError("Failed to parse response", response.status, response.statusText); } - } catch (error) { - console.error("Failed to parse response as JSON:", error); - throw new AppError("Failed to parse response", response.status, response.statusText); - } - // Validate response - if (responseType === ResponseType.JSON) { - if (!responseSchema) { + // Validate response + if (responseType === ResponseType.JSON) { + if (!responseSchema) { + return data; + } + const parsedData = responseSchema.safeParse(data); + + if (!parsedData.success) { + /** The request was successful, but the response data does not match the expected schema. + * This is a client side error, and should be fixed by updating the responseSchema to keep things typed. + * This error should not be shown to the user , it is for dev only. + */ + console.error(`Invalid API response schema - ${url} :`, JSON.stringify(parsedData.error)); + } + return data; } - const parsedData = responseSchema.safeParse(data); - - if (!parsedData.success) { - /** The request was successful, but the response data does not match the expected schema. - * This is a client side error, and should be fixed by updating the responseSchema to keep things typed. - * This error should not be shown to the user , it is for dev only. - */ - console.error(`Invalid API response schema - ${url} :`, JSON.stringify(parsedData.error)); - } return data; - } - - return data; } catch (error) { console.error("Request failed:", JSON.stringify(error)); throw error;