"use client"; import { useQuery } from "@tanstack/react-query"; import { usePathname } from "next/navigation"; import { useTranslations } from "next-intl"; import React, { useEffect, useState } from "react"; import { Breadcrumb, BreadcrumbItem, BreadcrumbLink, BreadcrumbList, BreadcrumbPage, BreadcrumbSeparator, } from "@/components/ui/breadcrumb"; import { searchSpacesApiService } from "@/lib/apis/search-spaces-api.service"; import { authenticatedFetch, getBearerToken } from "@/lib/auth-utils"; import { getThreadFull } from "@/lib/chat/thread-persistence"; import { cacheKeys } from "@/lib/query-client/cache-keys"; interface BreadcrumbItemInterface { label: string; href?: string; } export function DashboardBreadcrumb() { const t = useTranslations("breadcrumb"); const pathname = usePathname(); // Extract search space ID and chat ID from pathname const segments = pathname.split("/").filter(Boolean); const searchSpaceId = segments[0] === "dashboard" && segments[1] ? segments[1] : null; const { data: searchSpace } = useQuery({ queryKey: cacheKeys.searchSpaces.detail(searchSpaceId || ""), queryFn: () => searchSpacesApiService.getSearchSpace({ id: Number(searchSpaceId) }), enabled: !!searchSpaceId, }); // Extract chat thread ID from pathname for chat pages const chatThreadId = segments[2] === "new-chat" && segments[3] ? segments[3] : null; // Fetch thread details when on a chat page with a thread ID const { data: threadData } = useQuery({ queryKey: ["threads", searchSpaceId, "detail", chatThreadId], queryFn: () => getThreadFull(Number(chatThreadId)), enabled: !!chatThreadId && !!searchSpaceId, }); // State to store document title for editor breadcrumb const [documentTitle, setDocumentTitle] = useState(null); // Fetch document title when on editor page useEffect(() => { if (segments[2] === "editor" && segments[3] && searchSpaceId) { const documentId = segments[3]; // Skip fetch for "new" notes if (documentId === "new") { setDocumentTitle(null); return; } const token = getBearerToken(); if (token) { authenticatedFetch( `${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/search-spaces/${searchSpaceId}/documents/${documentId}/editor-content`, { method: "GET" } ) .then((res) => res.json()) .then((data) => { if (data.title) { setDocumentTitle(data.title); } }) .catch(() => { // If fetch fails, just use the document ID setDocumentTitle(null); }); } } else { setDocumentTitle(null); } }, [segments, searchSpaceId]); // Parse the pathname to create breadcrumb items const generateBreadcrumbs = (path: string): BreadcrumbItemInterface[] => { const segments = path.split("/").filter(Boolean); const breadcrumbs: BreadcrumbItemInterface[] = []; // Handle search space (start directly with search space, skip "Dashboard") if (segments[0] === "dashboard" && segments[1]) { // Use the actual search space name if available, otherwise fall back to the ID const searchSpaceLabel = searchSpace?.name || `${t("search_space")} ${segments[1]}`; breadcrumbs.push({ label: searchSpaceLabel, href: `/dashboard/${segments[1]}`, }); // Handle specific sections if (segments[2]) { const section = segments[2]; let sectionLabel = section.charAt(0).toUpperCase() + section.slice(1); // Map section names to more readable labels const sectionLabels: Record = { "new-chat": t("chat") || "Chat", documents: t("documents"), logs: t("logs"), settings: t("settings"), editor: t("editor"), }; sectionLabel = sectionLabels[section] || sectionLabel; // Handle sub-sections if (segments[3]) { const subSection = segments[3]; // Handle editor sub-sections (document ID) if (section === "editor") { // Handle special cases for editor let documentLabel: string; if (subSection === "new") { documentLabel = "New Note"; } else { documentLabel = documentTitle || subSection; } breadcrumbs.push({ label: t("documents"), href: `/dashboard/${segments[1]}/documents`, }); breadcrumbs.push({ label: sectionLabel, href: `/dashboard/${segments[1]}/documents`, }); breadcrumbs.push({ label: documentLabel }); return breadcrumbs; } // Handle documents sub-sections if (section === "documents") { const documentLabels: Record = { upload: t("upload_documents"), webpage: t("add_webpages"), }; const documentLabel = documentLabels[subSection] || subSection; breadcrumbs.push({ label: t("documents"), href: `/dashboard/${segments[1]}/documents`, }); breadcrumbs.push({ label: documentLabel }); return breadcrumbs; } // Handle new-chat sub-sections (thread IDs) // Show the chat title if available, otherwise fall back to "Chat" if (section === "new-chat") { const chatLabel = threadData?.title || t("chat") || "Chat"; breadcrumbs.push({ label: chatLabel, }); return breadcrumbs; } // Handle other sub-sections let subSectionLabel = subSection.charAt(0).toUpperCase() + subSection.slice(1); const subSectionLabels: Record = { upload: t("upload_documents"), youtube: t("add_youtube"), webpage: t("add_webpages"), manage: t("manage"), }; subSectionLabel = subSectionLabels[subSection] || subSectionLabel; breadcrumbs.push({ label: sectionLabel, href: `/dashboard/${segments[1]}/${section}`, }); breadcrumbs.push({ label: subSectionLabel }); } else { breadcrumbs.push({ label: sectionLabel }); } } } return breadcrumbs; }; const breadcrumbs = generateBreadcrumbs(pathname); if (breadcrumbs.length === 0) { return null; // Don't show breadcrumbs for root dashboard } return ( {breadcrumbs.map((item, index) => ( {index === breadcrumbs.length - 1 ? ( {item.label} ) : ( {item.label} )} {index < breadcrumbs.length - 1 && } ))} ); }