chore: linting

This commit is contained in:
DESKTOP-RTLN3BA\$punk 2026-01-12 14:17:15 -08:00
parent 11915df97b
commit 73a57589ac
25 changed files with 184 additions and 181 deletions

View file

@ -7,7 +7,6 @@ Revises: 59
from collections.abc import Sequence from collections.abc import Sequence
from alembic import op from alembic import op
from app.config import config from app.config import config
# revision identifiers, used by Alembic. # revision identifiers, used by Alembic.
@ -22,7 +21,7 @@ EMBEDDING_DIM = config.embedding_model_instance.dimension
def upgrade() -> None: def upgrade() -> None:
"""Create surfsense_docs_documents and surfsense_docs_chunks tables.""" """Create surfsense_docs_documents and surfsense_docs_chunks tables."""
# Create surfsense_docs_documents table # Create surfsense_docs_documents table
op.execute( op.execute(
f""" f"""
@ -46,7 +45,7 @@ def upgrade() -> None:
END$$; END$$;
""" """
) )
# Create indexes for surfsense_docs_documents # Create indexes for surfsense_docs_documents
op.execute( op.execute(
""" """
@ -75,7 +74,7 @@ def upgrade() -> None:
END$$; END$$;
""" """
) )
# Create surfsense_docs_chunks table # Create surfsense_docs_chunks table
op.execute( op.execute(
f""" f"""
@ -96,7 +95,7 @@ def upgrade() -> None:
END$$; END$$;
""" """
) )
# Create indexes for surfsense_docs_chunks # Create indexes for surfsense_docs_chunks
op.execute( op.execute(
""" """
@ -111,7 +110,7 @@ def upgrade() -> None:
END$$; END$$;
""" """
) )
# Create vector indexes for similarity search # Create vector indexes for similarity search
op.execute( op.execute(
""" """
@ -119,14 +118,14 @@ def upgrade() -> None:
ON surfsense_docs_documents USING hnsw (embedding public.vector_cosine_ops); ON surfsense_docs_documents USING hnsw (embedding public.vector_cosine_ops);
""" """
) )
op.execute( op.execute(
""" """
CREATE INDEX IF NOT EXISTS surfsense_docs_chunks_vector_index CREATE INDEX IF NOT EXISTS surfsense_docs_chunks_vector_index
ON surfsense_docs_chunks USING hnsw (embedding public.vector_cosine_ops); ON surfsense_docs_chunks USING hnsw (embedding public.vector_cosine_ops);
""" """
) )
# Create full-text search indexes (same pattern as documents/chunks tables) # Create full-text search indexes (same pattern as documents/chunks tables)
op.execute( op.execute(
""" """
@ -134,7 +133,7 @@ def upgrade() -> None:
ON surfsense_docs_documents USING gin (to_tsvector('english', content)); ON surfsense_docs_documents USING gin (to_tsvector('english', content));
""" """
) )
op.execute( op.execute(
""" """
CREATE INDEX IF NOT EXISTS surfsense_docs_chunks_search_index CREATE INDEX IF NOT EXISTS surfsense_docs_chunks_search_index
@ -148,18 +147,17 @@ def downgrade() -> None:
# Drop full-text search indexes # Drop full-text search indexes
op.execute("DROP INDEX IF EXISTS surfsense_docs_chunks_search_index") op.execute("DROP INDEX IF EXISTS surfsense_docs_chunks_search_index")
op.execute("DROP INDEX IF EXISTS surfsense_docs_documents_search_index") op.execute("DROP INDEX IF EXISTS surfsense_docs_documents_search_index")
# Drop vector indexes # Drop vector indexes
op.execute("DROP INDEX IF EXISTS surfsense_docs_chunks_vector_index") op.execute("DROP INDEX IF EXISTS surfsense_docs_chunks_vector_index")
op.execute("DROP INDEX IF EXISTS surfsense_docs_documents_vector_index") op.execute("DROP INDEX IF EXISTS surfsense_docs_documents_vector_index")
# Drop regular indexes # Drop regular indexes
op.execute("DROP INDEX IF EXISTS ix_surfsense_docs_chunks_document_id") 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_updated_at")
op.execute("DROP INDEX IF EXISTS ix_surfsense_docs_documents_content_hash") op.execute("DROP INDEX IF EXISTS ix_surfsense_docs_documents_content_hash")
op.execute("DROP INDEX IF EXISTS ix_surfsense_docs_documents_source") op.execute("DROP INDEX IF EXISTS ix_surfsense_docs_documents_source")
# Drop tables (chunks first due to FK) # Drop tables (chunks first due to FK)
op.execute("DROP TABLE IF EXISTS surfsense_docs_chunks") op.execute("DROP TABLE IF EXISTS surfsense_docs_chunks")
op.execute("DROP TABLE IF EXISTS surfsense_docs_documents") op.execute("DROP TABLE IF EXISTS surfsense_docs_documents")

View file

@ -48,10 +48,12 @@ def format_surfsense_docs_results(results: list[tuple]) -> str:
"metadata": {"source": doc.source}, "metadata": {"source": doc.source},
"chunks": [], "chunks": [],
} }
grouped[doc.id]["chunks"].append({ grouped[doc.id]["chunks"].append(
"chunk_id": f"doc-{chunk.id}", {
"content": chunk.content, "chunk_id": f"doc-{chunk.id}",
}) "content": chunk.content,
}
)
# Render XML matching format_documents_for_context structure # Render XML matching format_documents_for_context structure
parts: list[str] = [] parts: list[str] = []
@ -70,7 +72,9 @@ def format_surfsense_docs_results(results: list[tuple]) -> str:
parts.append("<document_content>") parts.append("<document_content>")
for ch in g["chunks"]: for ch in g["chunks"]:
parts.append(f" <chunk id='{ch['chunk_id']}'><![CDATA[{ch['content']}]]></chunk>") parts.append(
f" <chunk id='{ch['chunk_id']}'><![CDATA[{ch['content']}]]></chunk>"
)
parts.append("</document_content>") parts.append("</document_content>")
parts.append("</document>") parts.append("</document>")
@ -157,4 +161,3 @@ def create_search_surfsense_docs_tool(db_session: AsyncSession):
) )
return search_surfsense_docs return search_surfsense_docs

View file

@ -436,7 +436,9 @@ class SurfsenseDocsDocument(BaseModel, TimestampMixin):
__tablename__ = "surfsense_docs_documents" __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) title = Column(String, nullable=False)
content = Column(Text, nullable=False) content = Column(Text, nullable=False)
content_hash = Column(String, nullable=False, index=True) # For detecting changes content_hash = Column(String, nullable=False, index=True) # For detecting changes

View file

@ -623,10 +623,7 @@ async def index_connector_content(
SearchSourceConnectorType.LUMA_CONNECTOR, SearchSourceConnectorType.LUMA_CONNECTOR,
]: ]:
# Default to today if no end_date provided (users can manually select future dates) # Default to today if no end_date provided (users can manually select future dates)
if end_date is None: indexing_to = today_str if end_date is None else end_date
indexing_to = today_str
else:
indexing_to = end_date
else: else:
# For non-calendar connectors, cap at today # For non-calendar connectors, cap at today
indexing_to = end_date if end_date else today_str indexing_to = end_date if end_date else today_str

View file

@ -24,4 +24,3 @@ class SurfsenseDocsDocumentWithChunksRead(BaseModel):
chunks: list[SurfsenseDocsChunkRead] chunks: list[SurfsenseDocsChunkRead]
model_config = ConfigDict(from_attributes=True) model_config = ConfigDict(from_attributes=True)

View file

@ -19,7 +19,12 @@ from app.db import SurfsenseDocsChunk, SurfsenseDocsDocument, async_session_make
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
# Path to docs relative to project root # 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]: def parse_mdx_frontmatter(content: str) -> tuple[str, str]:
@ -38,7 +43,7 @@ def parse_mdx_frontmatter(content: str) -> tuple[str, str]:
if match: if match:
frontmatter = match.group(1) frontmatter = match.group(1)
content_without_frontmatter = content[match.end():] content_without_frontmatter = content[match.end() :]
# Extract title from frontmatter # Extract title from frontmatter
title_match = re.search(r"^title:\s*(.+)$", frontmatter, re.MULTILINE) 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]: async def index_surfsense_docs(session: AsyncSession) -> tuple[int, int, int, int]:
""" """
Index all Surfsense documentation files. Index all Surfsense documentation files.
Args: Args:
session: SQLAlchemy async session session: SQLAlchemy async session
Returns: Returns:
Tuple of (created, updated, skipped, deleted) counts 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 updated = 0
skipped = 0 skipped = 0
deleted = 0 deleted = 0
# Get all existing docs from database # Get all existing docs from database
existing_docs_result = await session.execute( 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()} existing_docs = {doc.source: doc for doc in existing_docs_result.scalars().all()}
# Track which sources we've processed # Track which sources we've processed
processed_sources = set() processed_sources = set()
# Get all MDX files # Get all MDX files
mdx_files = get_all_mdx_files() mdx_files = get_all_mdx_files()
logger.info(f"Found {len(mdx_files)} MDX files to index") logger.info(f"Found {len(mdx_files)} MDX files to index")
for mdx_file in mdx_files: for mdx_file in mdx_files:
try: try:
source = str(mdx_file.relative_to(DOCS_DIR)) source = str(mdx_file.relative_to(DOCS_DIR))
processed_sources.add(source) processed_sources.add(source)
# Read file content # Read file content
raw_content = mdx_file.read_text(encoding="utf-8") raw_content = mdx_file.read_text(encoding="utf-8")
title, content = parse_mdx_frontmatter(raw_content) title, content = parse_mdx_frontmatter(raw_content)
content_hash = generate_surfsense_docs_content_hash(raw_content) content_hash = generate_surfsense_docs_content_hash(raw_content)
if source in existing_docs: if source in existing_docs:
existing_doc = existing_docs[source] existing_doc = existing_docs[source]
# Check if content changed # Check if content changed
if existing_doc.content_hash == content_hash: if existing_doc.content_hash == content_hash:
logger.debug(f"Skipping unchanged: {source}") logger.debug(f"Skipping unchanged: {source}")
skipped += 1 skipped += 1
continue continue
# Content changed - update document # Content changed - update document
logger.info(f"Updating changed document: {source}") logger.info(f"Updating changed document: {source}")
# Create new chunks # Create new chunks
chunks = create_surfsense_docs_chunks(content) chunks = create_surfsense_docs_chunks(content)
# Update document fields # Update document fields
existing_doc.title = title existing_doc.title = title
existing_doc.content = content 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.embedding = config.embedding_model_instance.embed(content)
existing_doc.chunks = chunks existing_doc.chunks = chunks
existing_doc.updated_at = datetime.now(UTC) existing_doc.updated_at = datetime.now(UTC)
updated += 1 updated += 1
else: else:
# New document - create it # New document - create it
logger.info(f"Creating new document: {source}") logger.info(f"Creating new document: {source}")
chunks = create_surfsense_docs_chunks(content) chunks = create_surfsense_docs_chunks(content)
document = SurfsenseDocsDocument( document = SurfsenseDocsDocument(
source=source, source=source,
title=title, title=title,
@ -167,56 +174,56 @@ async def index_surfsense_docs(session: AsyncSession) -> tuple[int, int, int, in
chunks=chunks, chunks=chunks,
updated_at=datetime.now(UTC), updated_at=datetime.now(UTC),
) )
session.add(document) session.add(document)
created += 1 created += 1
except Exception as e: except Exception as e:
logger.error(f"Error processing {mdx_file}: {e}", exc_info=True) logger.error(f"Error processing {mdx_file}: {e}", exc_info=True)
continue continue
# Delete documents for removed files # Delete documents for removed files
for source, doc in existing_docs.items(): for source, doc in existing_docs.items():
if source not in processed_sources: if source not in processed_sources:
logger.info(f"Deleting removed document: {source}") logger.info(f"Deleting removed document: {source}")
await session.delete(doc) await session.delete(doc)
deleted += 1 deleted += 1
# Commit all changes # Commit all changes
await session.commit() await session.commit()
logger.info( logger.info(
f"Indexing complete: {created} created, {updated} updated, " f"Indexing complete: {created} created, {updated} updated, "
f"{skipped} skipped, {deleted} deleted" f"{skipped} skipped, {deleted} deleted"
) )
return created, updated, skipped, deleted return created, updated, skipped, deleted
async def seed_surfsense_docs() -> tuple[int, int, int, int]: async def seed_surfsense_docs() -> tuple[int, int, int, int]:
""" """
Seed Surfsense documentation into the database. Seed Surfsense documentation into the database.
This function indexes all MDX files from the docs directory. This function indexes all MDX files from the docs directory.
It handles creating, updating, and deleting docs based on content changes. It handles creating, updating, and deleting docs based on content changes.
Returns: Returns:
Tuple of (created, updated, skipped, deleted) counts Tuple of (created, updated, skipped, deleted) counts
Returns (0, 0, 0, 0) if an error occurs Returns (0, 0, 0, 0) if an error occurs
""" """
logger.info("Starting Surfsense docs indexing...") logger.info("Starting Surfsense docs indexing...")
try: try:
async with async_session_maker() as session: async with async_session_maker() as session:
created, updated, skipped, deleted = await index_surfsense_docs(session) created, updated, skipped, deleted = await index_surfsense_docs(session)
logger.info( logger.info(
f"Surfsense docs indexing complete: " f"Surfsense docs indexing complete: "
f"created={created}, updated={updated}, skipped={skipped}, deleted={deleted}" f"created={created}, updated={updated}, skipped={skipped}, deleted={deleted}"
) )
return created, updated, skipped, deleted return created, updated, skipped, deleted
except Exception as e: except Exception as e:
logger.error(f"Failed to seed Surfsense docs: {e}", exc_info=True) logger.error(f"Failed to seed Surfsense docs: {e}", exc_info=True)
return 0, 0, 0, 0 return 0, 0, 0, 0

View file

@ -24,9 +24,9 @@ def main():
print("=" * 50) print("=" * 50)
print(" Surfsense Documentation Seeding") print(" Surfsense Documentation Seeding")
print("=" * 50) print("=" * 50)
created, updated, skipped, deleted = asyncio.run(seed_surfsense_docs()) created, updated, skipped, deleted = asyncio.run(seed_surfsense_docs())
print() print()
print("Results:") print("Results:")
print(f" Created: {created}") print(f" Created: {created}")

View file

@ -105,9 +105,7 @@ function EmptyState({ onCreateClick }: { onCreateClick: () => void }) {
<div className="flex flex-col gap-2"> <div className="flex flex-col gap-2">
<h1 className="text-2xl font-bold">{t("welcome_title")}</h1> <h1 className="text-2xl font-bold">{t("welcome_title")}</h1>
<p className="max-w-md text-muted-foreground"> <p className="max-w-md text-muted-foreground">{t("welcome_description")}</p>
{t("welcome_description")}
</p>
</div> </div>
<Button size="lg" onClick={onCreateClick} className="gap-2"> <Button size="lg" onClick={onCreateClick} className="gap-2">
@ -123,11 +121,7 @@ export default function DashboardPage() {
const router = useRouter(); const router = useRouter();
const [showCreateDialog, setShowCreateDialog] = useState(false); const [showCreateDialog, setShowCreateDialog] = useState(false);
const { const { data: searchSpaces = [], isLoading, error } = useAtomValue(searchSpacesAtom);
data: searchSpaces = [],
isLoading,
error,
} = useAtomValue(searchSpacesAtom);
useEffect(() => { useEffect(() => {
if (isLoading) return; if (isLoading) return;

View file

@ -17,12 +17,7 @@ import { useTranslations } from "next-intl";
import { useCallback, useState } from "react"; import { useCallback, useState } from "react";
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "@/components/ui/tooltip";
import { useApiKey } from "@/hooks/use-api-key"; import { useApiKey } from "@/hooks/use-api-key";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
@ -249,16 +244,10 @@ function ApiKeyContent({ onMenuClick }: { onMenuClick: () => void }) {
onClick={copyToClipboard} onClick={copyToClipboard}
className="shrink-0" className="shrink-0"
> >
{copied ? ( {copied ? <Check className="h-4 w-4" /> : <Copy className="h-4 w-4" />}
<Check className="h-4 w-4" />
) : (
<Copy className="h-4 w-4" />
)}
</Button> </Button>
</TooltipTrigger> </TooltipTrigger>
<TooltipContent> <TooltipContent>{copied ? t("copied") : t("copy")}</TooltipContent>
{copied ? t("copied") : t("copy")}
</TooltipContent>
</Tooltip> </Tooltip>
</TooltipProvider> </TooltipProvider>
</div> </div>

View file

@ -3,8 +3,8 @@
import { AlertTriangle, Ban, Wrench } from "lucide-react"; import { AlertTriangle, Ban, Wrench } from "lucide-react";
import type { FC } from "react"; import type { FC } from "react";
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"; import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
import type { ConnectorStatus } from "../config/connector-status-config";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
import type { ConnectorStatus } from "../config/connector-status-config";
interface ConnectorStatusBadgeProps { interface ConnectorStatusBadgeProps {
status: ConnectorStatus; status: ConnectorStatus;

View file

@ -8,8 +8,8 @@ import { getConnectorIcon } from "@/contracts/enums/connectorIcons";
import type { SearchSourceConnector } from "@/contracts/types/connector.types"; import type { SearchSourceConnector } from "@/contracts/types/connector.types";
import type { LogActiveTask, LogSummary } from "@/contracts/types/log.types"; import type { LogActiveTask, LogSummary } from "@/contracts/types/log.types";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
import { getConnectorDisplayName } from "../tabs/all-connectors-tab";
import { useConnectorStatus } from "../hooks/use-connector-status"; import { useConnectorStatus } from "../hooks/use-connector-status";
import { getConnectorDisplayName } from "../tabs/all-connectors-tab";
interface ConnectorAccountsListViewProps { interface ConnectorAccountsListViewProps {
connectorType: string; connectorType: string;

View file

@ -15,7 +15,11 @@ interface InlineCitationProps {
* Renders a clickable numbered badge that opens the SourceDetailPanel with document chunk details. * Renders a clickable numbered badge that opens the SourceDetailPanel with document chunk details.
* Supports both regular knowledge base chunks and Surfsense documentation chunks. * Supports both regular knowledge base chunks and Surfsense documentation chunks.
*/ */
export const InlineCitation: FC<InlineCitationProps> = ({ chunkId, citationNumber, isDocsChunk = false }) => { export const InlineCitation: FC<InlineCitationProps> = ({
chunkId,
citationNumber,
isDocsChunk = false,
}) => {
const [isOpen, setIsOpen] = useState(false); const [isOpen, setIsOpen] = useState(false);
return ( return (

View file

@ -6,9 +6,9 @@ export type {
NavItem, NavItem,
NoteItem, NoteItem,
PageUsage, PageUsage,
SearchSpace,
SidebarSectionProps, SidebarSectionProps,
User, User,
SearchSpace,
} from "./types/layout.types"; } from "./types/layout.types";
export { export {
AllSearchSpacesSheet, AllSearchSpacesSheet,
@ -23,10 +23,10 @@ export {
NavSection, NavSection,
NoteListItem, NoteListItem,
PageUsageDisplay, PageUsageDisplay,
SearchSpaceAvatar,
Sidebar, Sidebar,
SidebarCollapseButton, SidebarCollapseButton,
SidebarHeader, SidebarHeader,
SidebarSection, SidebarSection,
SidebarUserProfile, SidebarUserProfile,
SearchSpaceAvatar,
} from "./ui"; } from "./ui";

View file

@ -28,8 +28,8 @@ import { resetUser, trackLogout } from "@/lib/posthog/events";
import { cacheKeys } from "@/lib/query-client/cache-keys"; import { cacheKeys } from "@/lib/query-client/cache-keys";
import type { ChatItem, NavItem, NoteItem, SearchSpace } from "../types/layout.types"; import type { ChatItem, NavItem, NoteItem, SearchSpace } from "../types/layout.types";
import { CreateSearchSpaceDialog } from "../ui/dialogs"; import { CreateSearchSpaceDialog } from "../ui/dialogs";
import { LayoutShell } from "../ui/shell";
import { AllSearchSpacesSheet } from "../ui/sheets"; import { AllSearchSpacesSheet } from "../ui/sheets";
import { LayoutShell } from "../ui/shell";
import { AllChatsSidebar } from "../ui/sidebar/AllChatsSidebar"; import { AllChatsSidebar } from "../ui/sidebar/AllChatsSidebar";
import { AllNotesSidebar } from "../ui/sidebar/AllNotesSidebar"; import { AllNotesSidebar } from "../ui/sidebar/AllNotesSidebar";

View file

@ -104,11 +104,7 @@ export function CreateSearchSpaceDialog({ open, onOpenChange }: CreateSearchSpac
<FormItem> <FormItem>
<FormLabel>{t("name_label")}</FormLabel> <FormLabel>{t("name_label")}</FormLabel>
<FormControl> <FormControl>
<Input <Input placeholder={t("name_placeholder")} {...field} autoFocus />
placeholder={t("name_placeholder")}
{...field}
autoFocus
/>
</FormControl> </FormControl>
<FormMessage /> <FormMessage />
</FormItem> </FormItem>
@ -163,4 +159,3 @@ export function CreateSearchSpaceDialog({ open, onOpenChange }: CreateSearchSpac
</Dialog> </Dialog>
); );
} }

View file

@ -1,2 +1 @@
export { CreateSearchSpaceDialog } from "./CreateSearchSpaceDialog"; export { CreateSearchSpaceDialog } from "./CreateSearchSpaceDialog";

View file

@ -42,7 +42,12 @@ function getInitials(name: string): string {
return name.slice(0, 2).toUpperCase(); 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 bgColor = stringToColor(name);
const initials = getInitials(name); const initials = getInitials(name);
const sizeClasses = size === "sm" ? "h-8 w-8 text-xs" : "h-10 w-10 text-sm"; const sizeClasses = size === "sm" ? "h-8 w-8 text-xs" : "h-10 w-10 text-sm";

View file

@ -1,8 +1,8 @@
export { CreateSearchSpaceDialog } from "./dialogs"; export { CreateSearchSpaceDialog } from "./dialogs";
export { Header } from "./header"; export { Header } from "./header";
export { IconRail, NavIcon, SearchSpaceAvatar } from "./icon-rail"; export { IconRail, NavIcon, SearchSpaceAvatar } from "./icon-rail";
export { LayoutShell } from "./shell";
export { AllSearchSpacesSheet } from "./sheets"; export { AllSearchSpacesSheet } from "./sheets";
export { LayoutShell } from "./shell";
export { export {
ChatListItem, ChatListItem,
MobileSidebar, MobileSidebar,

View file

@ -1,6 +1,15 @@
"use client"; "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 { useTranslations } from "next-intl";
import { useState } from "react"; import { useState } from "react";
import { import {
@ -112,9 +121,7 @@ export function AllSearchSpacesSheet({
</div> </div>
<div className="flex flex-col gap-1"> <div className="flex flex-col gap-1">
<p className="font-medium">{t("no_search_spaces")}</p> <p className="font-medium">{t("no_search_spaces")}</p>
<p className="text-sm text-muted-foreground"> <p className="text-sm text-muted-foreground">{t("create_first_search_space")}</p>
{t("create_first_search_space")}
</p>
</div> </div>
{onCreateNew && ( {onCreateNew && (
<Button onClick={onCreateNew} className="mt-2"> <Button onClick={onCreateNew} className="mt-2">
@ -132,9 +139,7 @@ export function AllSearchSpacesSheet({
> >
<div className="flex items-start justify-between gap-2"> <div className="flex items-start justify-between gap-2">
<div className="flex flex-1 flex-col gap-1"> <div className="flex flex-1 flex-col gap-1">
<span className="font-medium leading-tight"> <span className="font-medium leading-tight">{space.name}</span>
{space.name}
</span>
{space.description && ( {space.description && (
<span className="text-sm text-muted-foreground line-clamp-2"> <span className="text-sm text-muted-foreground line-clamp-2">
{space.description} {space.description}
@ -143,42 +148,42 @@ export function AllSearchSpacesSheet({
</div> </div>
<div className="flex shrink-0 items-center gap-2"> <div className="flex shrink-0 items-center gap-2">
{space.memberCount > 1 && ( {space.memberCount > 1 && (
<Badge variant="outline" className="shrink-0"> <Badge variant="outline" className="shrink-0">
<Share2 className="mr-1 h-3 w-3" /> <Share2 className="mr-1 h-3 w-3" />
{tCommon("shared")} {tCommon("shared")}
</Badge> </Badge>
)} )}
{space.isOwner && ( {space.isOwner && (
<DropdownMenu> <DropdownMenu>
<DropdownMenuTrigger asChild> <DropdownMenuTrigger asChild>
<Button <Button
variant="ghost" variant="ghost"
size="icon" size="icon"
className="h-6 w-6 shrink-0" className="h-6 w-6 shrink-0"
onClick={(e) => e.stopPropagation()} onClick={(e) => e.stopPropagation()}
> >
<MoreHorizontal className="h-4 w-4" /> <MoreHorizontal className="h-4 w-4" />
</Button> </Button>
</DropdownMenuTrigger> </DropdownMenuTrigger>
<DropdownMenuContent align="end"> <DropdownMenuContent align="end">
<DropdownMenuItem onClick={(e) => handleSettings(e, space)}> <DropdownMenuItem onClick={(e) => handleSettings(e, space)}>
<Settings className="mr-2 h-4 w-4" /> <Settings className="mr-2 h-4 w-4" />
{tCommon("settings")} {tCommon("settings")}
</DropdownMenuItem> </DropdownMenuItem>
<DropdownMenuSeparator /> <DropdownMenuSeparator />
<DropdownMenuItem <DropdownMenuItem
onClick={(e) => handleDeleteClick(e, space)} onClick={(e) => handleDeleteClick(e, space)}
className="text-destructive focus:text-destructive" className="text-destructive focus:text-destructive"
> >
<Trash2 className="mr-2 h-4 w-4" /> <Trash2 className="mr-2 h-4 w-4" />
{tCommon("delete")} {tCommon("delete")}
</DropdownMenuItem> </DropdownMenuItem>
</DropdownMenuContent> </DropdownMenuContent>
</DropdownMenu> </DropdownMenu>
)} )}
</div> </div>
</div> </div>
<div className="flex items-center gap-4 text-xs text-muted-foreground"> <div className="flex items-center gap-4 text-xs text-muted-foreground">

View file

@ -1,2 +1 @@
export { AllSearchSpacesSheet } from "./AllSearchSpacesSheet"; export { AllSearchSpacesSheet } from "./AllSearchSpacesSheet";

View file

@ -10,8 +10,8 @@ import type {
NavItem, NavItem,
NoteItem, NoteItem,
PageUsage, PageUsage,
User,
SearchSpace, SearchSpace,
User,
} from "../../types/layout.types"; } from "../../types/layout.types";
import { Header } from "../header"; import { Header } from "../header";
import { IconRail } from "../icon-rail"; import { IconRail } from "../icon-rail";

View file

@ -9,8 +9,8 @@ import type {
NavItem, NavItem,
NoteItem, NoteItem,
PageUsage, PageUsage,
User,
SearchSpace, SearchSpace,
User,
} from "../../types/layout.types"; } from "../../types/layout.types";
import { IconRail } from "../icon-rail"; import { IconRail } from "../icon-rail";
import { Sidebar } from "./Sidebar"; import { Sidebar } from "./Sidebar";

View file

@ -11,8 +11,8 @@ import type {
NavItem, NavItem,
NoteItem, NoteItem,
PageUsage, PageUsage,
User,
SearchSpace, SearchSpace,
User,
} from "../../types/layout.types"; } from "../../types/layout.types";
import { ChatListItem } from "./ChatListItem"; import { ChatListItem } from "./ChatListItem";
import { NavSection } from "./NavSection"; import { NavSection } from "./NavSection";
@ -289,7 +289,12 @@ export function Sidebar({
<PageUsageDisplay pagesUsed={pageUsage.pagesUsed} pagesLimit={pageUsage.pagesLimit} /> <PageUsageDisplay pagesUsed={pageUsage.pagesUsed} pagesLimit={pageUsage.pagesLimit} />
)} )}
<SidebarUserProfile user={user} onUserSettings={onUserSettings} onLogout={onLogout} isCollapsed={isCollapsed} /> <SidebarUserProfile
user={user}
onUserSettings={onUserSettings}
onLogout={onLogout}
isCollapsed={isCollapsed}
/>
</div> </div>
</div> </div>
); );

View file

@ -43,7 +43,9 @@ export function SidebarHeader({
isCollapsed ? "w-10" : "w-50" isCollapsed ? "w-10" : "w-50"
)} )}
> >
<span className="truncate text-base">{searchSpace?.name ?? t("select_search_space")}</span> <span className="truncate text-base">
{searchSpace?.name ?? t("select_search_space")}
</span>
<ChevronsUpDown className="h-4 w-4 shrink-0 text-muted-foreground" /> <ChevronsUpDown className="h-4 w-4 shrink-0 text-muted-foreground" />
</Button> </Button>
</DropdownMenuTrigger> </DropdownMenuTrigger>

View file

@ -161,52 +161,52 @@ class BaseApiService {
} }
} }
// biome-ignore lint/suspicious: Unknown // biome-ignore lint/suspicious: Unknown
let data; let data;
const responseType = mergedOptions.responseType; const responseType = mergedOptions.responseType;
try { try {
switch (responseType) { switch (responseType) {
case ResponseType.JSON: case ResponseType.JSON:
data = await response.json(); data = await response.json();
break; break;
case ResponseType.TEXT: case ResponseType.TEXT:
data = await response.text(); data = await response.text();
break; break;
case ResponseType.BLOB: case ResponseType.BLOB:
data = await response.blob(); data = await response.blob();
break; break;
case ResponseType.ARRAY_BUFFER: case ResponseType.ARRAY_BUFFER:
data = await response.arrayBuffer(); data = await response.arrayBuffer();
break; break;
// Add more cases as needed // Add more cases as needed
default: default:
data = await response.json(); 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 // Validate response
if (responseType === ResponseType.JSON) { if (responseType === ResponseType.JSON) {
if (!responseSchema) { 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; 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;
}
return data;
} catch (error) { } catch (error) {
console.error("Request failed:", JSON.stringify(error)); console.error("Request failed:", JSON.stringify(error));
throw error; throw error;