From 79a58ef5249c54d6b5c00da6c6024bbe287906f6 Mon Sep 17 00:00:00 2001 From: Roberto 'Andler' Lucas Date: Mon, 1 Dec 2025 20:35:58 -0600 Subject: [PATCH 01/22] fix: dept from path guard --- apps/pro-web/app/(pro)/[category]/page.tsx | 2 +- .../components/layout/header/user-menu.tsx | 4 +- .../layout/sidebar/profile-sidebar.tsx | 4 +- apps/pro-web/components/routes/pro/pro.tsx | 13 +++-- .../workspace-document-breadcrumb.tsx | 8 +-- apps/pro-web/lib/hooks/use-workspace.tsx | 53 ++++++++++++++----- 6 files changed, 59 insertions(+), 25 deletions(-) diff --git a/apps/pro-web/app/(pro)/[category]/page.tsx b/apps/pro-web/app/(pro)/[category]/page.tsx index f954434ac..1ad02cdc9 100644 --- a/apps/pro-web/app/(pro)/[category]/page.tsx +++ b/apps/pro-web/app/(pro)/[category]/page.tsx @@ -56,7 +56,7 @@ export default async function ChatCategoryPage(props: PageProps) { return ( <> - + ) diff --git a/apps/pro-web/components/layout/header/user-menu.tsx b/apps/pro-web/components/layout/header/user-menu.tsx index fc5bc42d3..812faff8a 100644 --- a/apps/pro-web/components/layout/header/user-menu.tsx +++ b/apps/pro-web/components/layout/header/user-menu.tsx @@ -129,7 +129,7 @@ export function UserMenu({ user }: UserMenuProps) { - + {/* - + */} - Organization - + */} (null) const threadRef = useRef(null) const params = useParams<{ chatbot: string; threadId: string }>() @@ -104,7 +108,8 @@ export function Pro({ } }, [isLoading]) - const shouldShowChatPanel = showChatPanel || activeThread || activeChatbot + const shouldShowChatPanel = + showChatPanel || activeThread || activeChatbot || isWorkspaceActive return ( <> @@ -121,7 +126,7 @@ export function Pro({ {shouldShowChatPanel && ( - {/* Document Type Crumb */} + {/* Document Type Crumb + */} {/* Department Change Dialog */} diff --git a/apps/pro-web/lib/hooks/use-workspace.tsx b/apps/pro-web/lib/hooks/use-workspace.tsx index 5b01ce731..0352a1bcd 100644 --- a/apps/pro-web/lib/hooks/use-workspace.tsx +++ b/apps/pro-web/lib/hooks/use-workspace.tsx @@ -33,7 +33,7 @@ import type { WorkspaceStatePayload, WorkspaceTabType, } from '@/types/thread.types' -import { debounce } from 'lodash' +import { debounce, uniq } from 'lodash' import { appConfig } from 'mb-env' import { useSession } from 'next-auth/react' import { usePathname } from 'next/navigation' @@ -273,13 +273,34 @@ export function WorkspaceProvider({ // biome-ignore lint/correctness/useExhaustiveDependencies: useEffect(() => { if ( - departmentFromPathname === activeDepartment || - !departmentFromPathname + !departmentFromPathname || + !departmentFromPathname.match(/(general|marketing|product-service)/) ) { return } - const capitalizedDepartment = `${departmentFromPathname?.slice(0, 1).toUpperCase()}${departmentFromPathname?.slice(1)}` + const normalizedDepartmentFromPathname = departmentFromPathname.split('-') + const capitalizedDepartment = normalizedDepartmentFromPathname + .map((chunk) => { + const capitalizedChunk = `${chunk?.slice(0, 1).toUpperCase()}${chunk?.slice(1)}` + return capitalizedChunk + }) + .join('/') + + if (capitalizedDepartment === activeDepartment?.[0]) { + return + } + + const deptProjects = + projectsByDept[capitalizedDepartment as keyof typeof projectsByDept] + const firstDoc = userDocuments.find( + (uDoc) => + uDoc.organization === activeOrganization && + uDoc.project && + deptProjects && + deptProjects[uDoc.project as keyof typeof deptProjects], + ) + setActiveDepartment([ capitalizedDepartment, Number( @@ -288,11 +309,15 @@ export function WorkspaceProvider({ ], ), ]) - const deptProjects = - projectsByDept[capitalizedDepartment as keyof typeof projectsByDept] setActiveProject( - Array.isArray(deptProjects) ? deptProjects[0] || null : null, + Array.isArray(deptProjects) + ? deptProjects[0] || firstDoc?.project || null + : null, ) + + if (activeDocument) { + setActiveDocument(firstDoc?.name || null) + } }, [departmentFromPathname, activeDepartment]) // Wrapper functions for TanStack mutations + local state management @@ -624,15 +649,18 @@ export function WorkspaceProvider({ // biome-ignore lint/correctness/useExhaustiveDependencies: const userDocuments = useMemo(() => { const draftDocuments = userOrphanedDocuments.data || [] - const userDocs = documents + const userDocs = uniq([ + ...(userThreadDocuments?.threadDocuments || []), + ...(userThreadDocuments?.allDocuments || []), + ...draftDocuments, + ]) // Filter documents by workspace context - department-based filtering const filteredDocs = userDocs.filter((document) => { - // Validate organization exists if ( - !organizationList.some((org) => org === document.organization) - // || !orgDepts?.some((dept) => dept === document.department) - // || !deptProjects?.some((proj) => proj === document.project) + !organizationList.some((org) => org === document.organization) || + !orgDepts?.some((dept) => dept === document.department) || + !deptProjects?.some((proj) => proj === document.project) ) { return false } @@ -658,6 +686,7 @@ export function WorkspaceProvider({ }) }, [ userThreadDocuments, + userOrphanedDocuments.data, activeDocument, activeProject, organizationList, From da775f98931377c540812e7d9c812b21016e34a7 Mon Sep 17 00:00:00 2001 From: Roberto 'Andler' Lucas Date: Mon, 1 Dec 2025 21:44:43 -0600 Subject: [PATCH 02/22] fix: workspace structure chk missing project in dept --- .../sidebar/sidebar-department-general.tsx | 23 ++++-- .../layout/sidebar/sidebar-link.tsx | 71 +++++++++++-------- .../routes/chat/prompt-form/index.tsx | 6 +- .../lib/queries/use-workspace-structure.ts | 46 +++++++++--- apps/pro-web/tailwind.config.js | 8 +-- 5 files changed, 102 insertions(+), 52 deletions(-) diff --git a/apps/pro-web/components/layout/sidebar/sidebar-department-general.tsx b/apps/pro-web/components/layout/sidebar/sidebar-department-general.tsx index c8d2645b9..dc89c40f9 100644 --- a/apps/pro-web/components/layout/sidebar/sidebar-department-general.tsx +++ b/apps/pro-web/components/layout/sidebar/sidebar-department-general.tsx @@ -1,10 +1,12 @@ 'use client' import { SidebarLink } from '@/components/layout/sidebar/sidebar-link' +import { Accordion } from '@/components/ui/accordion' import { useSidebar } from '@/lib/hooks/use-sidebar' export function SidebarDepartmentGeneral({ page }: { page?: string }) { - const { filteredDepartments, isFilterMode } = useSidebar() + const { filteredDepartments, isFilterMode, expandedDepartments } = + useSidebar() if (!filteredDepartments.length) return ( @@ -13,16 +15,23 @@ export function SidebarDepartmentGeneral({ page }: { page?: string }) { ) return ( -
    - {filteredDepartments.map((department) => ( -
  • + +
      + {filteredDepartments.map((department) => ( - - ))} -
    + ))} +
+ ) } diff --git a/apps/pro-web/components/layout/sidebar/sidebar-link.tsx b/apps/pro-web/components/layout/sidebar/sidebar-link.tsx index f3dbbbabf..e6f36abba 100755 --- a/apps/pro-web/components/layout/sidebar/sidebar-link.tsx +++ b/apps/pro-web/components/layout/sidebar/sidebar-link.tsx @@ -1,5 +1,11 @@ 'use client' +import { + Accordion, + AccordionContent, + AccordionItem, + AccordionTrigger, +} from '@/components/ui/accordion' import { Checkbox } from '@/components/ui/checkbox' import { IconCaretRight } from '@/components/ui/icons' import { useBrowse } from '@/lib/hooks/use-browse' @@ -164,17 +170,11 @@ export function SidebarLink({ /> )} {department.name} - ) const childrenContent = (isExpanded || isFilterMode || someBotsSelected) && ( -
+
{department.chatbots.map((chatbot) => ( - { - e.stopPropagation() - handleClickDepartment(e) - }} - role="menuitem" - aria-expanded={isActive} - aria-controls={`department-${department.name}`} - prefetch={false} - > - {departmentContent} - - {childrenContent} -
+ +
  • + + { + e.stopPropagation() + handleClickDepartment(e) + }} + role="menuitem" + aria-expanded={isActive} + aria-controls={`department-${department.name}`} + prefetch={false} + className="size-full px-4 py-3.5 " + > + {departmentContent} + + + {childrenContent} +
  • +
    ) } diff --git a/apps/pro-web/components/routes/chat/prompt-form/index.tsx b/apps/pro-web/components/routes/chat/prompt-form/index.tsx index 801503bc7..7f6b47e56 100644 --- a/apps/pro-web/components/routes/chat/prompt-form/index.tsx +++ b/apps/pro-web/components/routes/chat/prompt-form/index.tsx @@ -654,11 +654,11 @@ export function PromptForm({ 'hover:border-[#82e46a] hover:text-[#82e46a]', )} > - Select{' '} + Select a{' '} - {params.chatbot || 'a Masterbot'} + {params.chatbot || 'Masterbot'} {' '} - to continue + to start chat
    ) : null} diff --git a/apps/pro-web/lib/queries/use-workspace-structure.ts b/apps/pro-web/lib/queries/use-workspace-structure.ts index ee1ac33c3..fee1cecc4 100644 --- a/apps/pro-web/lib/queries/use-workspace-structure.ts +++ b/apps/pro-web/lib/queries/use-workspace-structure.ts @@ -321,26 +321,52 @@ export function useWorkspaceStructure( // --- Ensure projectsByDept and textDocuments are up to date with all documents --- + // First pass: collect all projects per organization + const projectsByOrg = new Map>() for (const doc of finalStructure.documents) { const org = doc.organization - const dept = doc.department const project = doc.project - const name = doc.name - const type = doc.type || 'text' - // Update projectsByDept - if (org && dept && project) { - if (!finalStructure.projectsByDept[org]) { - finalStructure.projectsByDept[org] = {} + if (org && project) { + if (!projectsByOrg.has(org)) { + projectsByOrg.set(org, new Set()) } + projectsByOrg.get(org)?.add(project) + } + } + + // Second pass: ensure all projects exist in all departments for each org + for (const [org, projects] of projectsByOrg.entries()) { + const departments = finalStructure.departmentsByOrg[org] || [] + + // Initialize projectsByDept for this org if needed + if (!finalStructure.projectsByDept[org]) { + finalStructure.projectsByDept[org] = {} + } + + // Ensure each department has all projects + for (const dept of departments) { if (!finalStructure.projectsByDept[org][dept]) { finalStructure.projectsByDept[org][dept] = [] } - if (!finalStructure.projectsByDept[org][dept].includes(project)) { - finalStructure.projectsByDept[org][dept].push(project) - finalStructure.projectsByDept[org][dept].sort() + + for (const project of projects) { + if ( + !finalStructure.projectsByDept[org][dept].includes(project) + ) { + finalStructure.projectsByDept[org][dept].push(project) + } } + + finalStructure.projectsByDept[org][dept].sort() } + } + + // Third pass: update type-specific document lists + for (const doc of finalStructure.documents) { + const project = doc.project + const name = doc.name + const type = doc.type || 'text' // Update textDocuments (only for text docs) if (type === 'text' && project && name) { diff --git a/apps/pro-web/tailwind.config.js b/apps/pro-web/tailwind.config.js index 80cc51b3d..936e62312 100644 --- a/apps/pro-web/tailwind.config.js +++ b/apps/pro-web/tailwind.config.js @@ -77,12 +77,12 @@ module.exports = { }, keyframes: { 'accordion-down': { - from: { height: 0 }, + from: { height: '0' }, to: { height: 'var(--radix-accordion-content-height)' }, }, 'accordion-up': { from: { height: 'var(--radix-accordion-content-height)' }, - to: { height: 0 }, + to: { height: '0' }, }, 'fade-in': { from: { @@ -101,8 +101,8 @@ module.exports = { mirage: 'hsl(var(--mirage))', }, animation: { - 'accordion-down': 'accordion-down 0.2s ease-out', - 'accordion-up': 'accordion-up 0.2s ease-out', + 'accordion-down': 'accordion-down 0.3s ease-in-out', + 'accordion-up': 'accordion-up 0.3s ease-in-out', 'fade-in': 'fade-in 0.5s ease-out', 'fade-out': 'fade-out 0.5s ease-out', }, From a2f3b80e79695ea93ea97fe41d2d000de800d746 Mon Sep 17 00:00:00 2001 From: Roberto 'Andler' Lucas Date: Tue, 2 Dec 2025 18:56:41 -0600 Subject: [PATCH 03/22] fix: department n threads navigation --- .../[threadQuestionSlug]/page.tsx | 1 + .../[domain]/[chatbot]/[threadSlug]/page.tsx | 1 + .../[category]/[domain]/[chatbot]/page.tsx | 1 + .../[category]/[domain]/[chatbot]/page.tsx | 1 + .../sidebar/sidebar-department-general.tsx | 10 +- .../routes/chat/prompt-form/index.tsx | 4 +- .../components/routes/thread/thread-list.tsx | 6 +- apps/pro-web/lib/hooks/use-sidebar.tsx | 111 ++++++++--- apps/pro-web/lib/hooks/use-workspace.tsx | 180 +----------------- .../lib/queries/use-workspace-mutations.ts | 1 - .../lib/queries/use-workspace-structure.ts | 46 ++--- apps/pro-web/lib/threads.ts | 8 +- 12 files changed, 126 insertions(+), 244 deletions(-) diff --git a/apps/pro-web/app/(pro)/[category]/[domain]/[chatbot]/[threadSlug]/[threadQuestionSlug]/page.tsx b/apps/pro-web/app/(pro)/[category]/[domain]/[chatbot]/[threadSlug]/[threadQuestionSlug]/page.tsx index f01ee7d36..1ac17f926 100644 --- a/apps/pro-web/app/(pro)/[category]/[domain]/[chatbot]/[threadSlug]/[threadQuestionSlug]/page.tsx +++ b/apps/pro-web/app/(pro)/[category]/[domain]/[chatbot]/[threadSlug]/[threadQuestionSlug]/page.tsx @@ -42,6 +42,7 @@ export default async function BotThreadPopUpQuestionPage(props: { redirect('/auth/signin') } + console.log('[chatbot] [threadQuestionSlug] route params', params) const chatbotName = (await botNames).get(params.chatbot) if (!chatbotName) { console.error( diff --git a/apps/pro-web/app/(pro)/[category]/[domain]/[chatbot]/[threadSlug]/page.tsx b/apps/pro-web/app/(pro)/[category]/[domain]/[chatbot]/[threadSlug]/page.tsx index 3e2467d35..112ea029c 100644 --- a/apps/pro-web/app/(pro)/[category]/[domain]/[chatbot]/[threadSlug]/page.tsx +++ b/apps/pro-web/app/(pro)/[category]/[domain]/[chatbot]/[threadSlug]/page.tsx @@ -42,6 +42,7 @@ export default async function BotThreadPopUpPage(props: { redirect('/auth/signin') } + console.log('[chatbot] [threadSlug] route params', params) const chatbotName = (await botNames).get(params.chatbot) if (!chatbotName) { console.error( diff --git a/apps/pro-web/app/(pro)/[category]/[domain]/[chatbot]/page.tsx b/apps/pro-web/app/(pro)/[category]/[domain]/[chatbot]/page.tsx index d6740ffcf..4c1d752cb 100644 --- a/apps/pro-web/app/(pro)/[category]/[domain]/[chatbot]/page.tsx +++ b/apps/pro-web/app/(pro)/[category]/[domain]/[chatbot]/page.tsx @@ -43,6 +43,7 @@ export default async function BotThreadsPage(props: { redirect('/auth/signin') } + console.log('[chatbot] route params', params) const chatbotName = (await botNames).get(params.chatbot) if (!chatbotName) { console.error( diff --git a/apps/pro-web/app/org/[category]/[domain]/[chatbot]/page.tsx b/apps/pro-web/app/org/[category]/[domain]/[chatbot]/page.tsx index 45985cf3f..803ff87b6 100644 --- a/apps/pro-web/app/org/[category]/[domain]/[chatbot]/page.tsx +++ b/apps/pro-web/app/org/[category]/[domain]/[chatbot]/page.tsx @@ -11,6 +11,7 @@ export default async function BrowseCategoryChatbotPage(props: { params: Promise<{ category: string; chatbot: string }> }) { const params = await props.params + console.log('[chatbot] [org] route params', params) const chatbotName = (await botNames).get(params.chatbot) if (!chatbotName) { console.error( diff --git a/apps/pro-web/components/layout/sidebar/sidebar-department-general.tsx b/apps/pro-web/components/layout/sidebar/sidebar-department-general.tsx index dc89c40f9..2c8c859ce 100644 --- a/apps/pro-web/components/layout/sidebar/sidebar-department-general.tsx +++ b/apps/pro-web/components/layout/sidebar/sidebar-department-general.tsx @@ -5,8 +5,12 @@ import { Accordion } from '@/components/ui/accordion' import { useSidebar } from '@/lib/hooks/use-sidebar' export function SidebarDepartmentGeneral({ page }: { page?: string }) { - const { filteredDepartments, isFilterMode, expandedDepartments } = - useSidebar() + const { + filteredDepartments, + isFilterMode, + expandedDepartments, + activeDepartment, + } = useSidebar() if (!filteredDepartments.length) return ( @@ -18,7 +22,7 @@ export function SidebarDepartmentGeneral({ page }: { page?: string }) { diff --git a/apps/pro-web/components/routes/chat/prompt-form/index.tsx b/apps/pro-web/components/routes/chat/prompt-form/index.tsx index 7f6b47e56..310f952ed 100644 --- a/apps/pro-web/components/routes/chat/prompt-form/index.tsx +++ b/apps/pro-web/components/routes/chat/prompt-form/index.tsx @@ -70,7 +70,7 @@ import { useSonner } from '@/lib/hooks/useSonner' import type { MarkdownSection } from '@/lib/markdown-utils' import { useDocumentContent } from '@/lib/queries/use-document-content' import { cn, nanoid } from '@/lib/utils' -import type { Attachment, ChatRequestOptions } from 'ai' +import { type Attachment, type ChatRequestOptions, generateId } from 'ai' import type { UseChatHelpers } from 'ai/react' import { motion } from 'framer-motion' import { BookPlusIcon, FilePlusIcon, PlusIcon, SaveIcon } from 'lucide-react' @@ -243,7 +243,7 @@ export function PromptForm({ // * Creating unique instances for each form (popup and main). // ? This is required to prevent the form from submitting when the user presses Enter in the popup. // ? Must be rendered once per form instance. Else it will not work as expected if leave without memoizing (onChange would update this component). - const formId = useMemo(() => nanoid(16), []) + const formId = useMemo(generateId, []) const handleBotSelection = () => { if (activeThread?.chatbot) { diff --git a/apps/pro-web/components/routes/thread/thread-list.tsx b/apps/pro-web/components/routes/thread/thread-list.tsx index dc2483c4f..55ffe1b75 100644 --- a/apps/pro-web/components/routes/thread/thread-list.tsx +++ b/apps/pro-web/components/routes/thread/thread-list.tsx @@ -51,6 +51,7 @@ export default function ThreadList({ selectedChatbots, activeDepartment, activeChatbot, + setActiveChatbot, } = useSidebar() const { activeThread, setActiveThread, setIsOpenPopup } = useThread() const [loadingThread, setLoadingThread] = useState(false) @@ -101,6 +102,7 @@ export default function ThreadList({ setLoadingThread(true) setActiveThread(thread) + setActiveChatbot(thread.chatbot) setIsOpenPopup(true) } catch (error) { console.error('Error activating thread popup:', error) @@ -117,14 +119,14 @@ export default function ThreadList({ customSonner, activateThreadPopup, ), - [], + [pathname], ) // biome-ignore lint/correctness/useExhaustiveDependencies: useEffect(() => { if (activeThread) return getOpeningActiveThread() - }, []) + }, [pathname, activeThread]) if (loadingThread) { return ( diff --git a/apps/pro-web/lib/hooks/use-sidebar.tsx b/apps/pro-web/lib/hooks/use-sidebar.tsx index a769a4686..73be248c4 100644 --- a/apps/pro-web/lib/hooks/use-sidebar.tsx +++ b/apps/pro-web/lib/hooks/use-sidebar.tsx @@ -1,6 +1,7 @@ 'use client' import { useFilteredDepartments } from '@/lib/hooks/use-filtered-departments' +import { useThread } from '@/lib/hooks/use-thread' import { urlBuilders } from '@/lib/url' import { getDepartments, getUserBySlug } from '@/services/hasura' import type { Chatbot, Department } from 'mb-genql' @@ -83,11 +84,17 @@ export function SidebarProvider({ children }: SidebarProviderProps) { // Get organization chatbots and department tuple from workspace core const { activeOrganization, + activeDepartment, + projectsByDept, + userDocuments, + isWorkspaceActive, getOrganizationChatbots, updateChatbotActiveState, - activeDepartment, setActiveDepartment, + setActiveDocument, + setActiveProject, } = useWorkspace() + const { activeThread } = useThread() // Extract numeric ID from tuple for backward compatibility const activeDepartmentId = activeDepartment?.[1] ?? null @@ -214,7 +221,12 @@ export function SidebarProvider({ children }: SidebarProviderProps) { // biome-ignore lint/correctness/useExhaustiveDependencies: React.useEffect(() => { - if (!pathname || !departments?.departmentsChatbots) return + if (!window.location.pathname || !departments?.departmentsChatbots) { + return + } + const pathnameSplit = pathname.split('/').filter(Boolean).length + ? pathname.split('/') + : window.location.pathname.split('/') const [ , publicTopicSlugOrBasePath, // c or :department @@ -225,43 +237,81 @@ export function SidebarProvider({ children }: SidebarProviderProps) { profileChatbot, // :chatbot _profileThreadSlug, // :threadSlug _profileThreadQuestionSlug, // :threadQuestionSlug - ] = pathname.split('/') + ] = pathnameSplit const department = departments.departmentsChatbots.find( (dep) => dep?.name && - (toSlug(dep.name) === personalChatbotSlugProfileTopicOrThreadSlug || - toSlug(dep.name) === personalProfilesTopicSlugOrDomainSlug || - toSlug(dep.name) === publicTopicSlugOrBasePath), + (toSlug(dep.name) === publicTopicSlugOrBasePath || + toSlug(dep.name) === personalProfilesTopicSlugOrDomainSlug), ) + + console.log('pathname split', pathname.split('/')) + console.log( + 'window.location.pathname split', + window.location.pathname.split('/'), + ) + console.log('department', department) + + // Normalize department name from pathname + const [proDepartmentPath, organizationDepartmentPath] = [ + publicTopicSlugOrBasePath?.split('-'), + personalProfilesTopicSlugOrDomainSlug?.split('-'), + ] + const [proCapitalizedPath, organizationCapitalizedPath] = [ + proDepartmentPath, + organizationDepartmentPath, + ] + .filter(Boolean) + .map((chunkMap) => { + return chunkMap + .map((chunk) => { + const capitalizedChunk = `${chunk?.slice(0, 1).toUpperCase()}${chunk?.slice(1)}` + return capitalizedChunk + }) + .join('-') + }) + if (department?.departmentId) { + const [proDeptProjects, organizationDeptProjects] = [ + projectsByDept[proCapitalizedPath as keyof typeof projectsByDept], + projectsByDept[ + organizationCapitalizedPath as keyof typeof projectsByDept + ], + ] + const firstDoc = userDocuments.find( + (uDoc) => + uDoc.organization === activeOrganization && + uDoc.project && + (proDeptProjects || organizationDeptProjects) && + (proDeptProjects[uDoc.project as keyof typeof proDeptProjects] || + organizationDeptProjects[ + uDoc.project as keyof typeof organizationDeptProjects + ]), + ) // Set department as tuple [name, id] setActiveDepartment([department.name, department.departmentId]) + setActiveProject( + Array.isArray(proDeptProjects) && proDeptProjects[0] + ? proDeptProjects[0] + : Array.isArray(organizationDeptProjects) && + organizationDeptProjects[0] + ? organizationDeptProjects[0] + : firstDoc?.project || null, + ) + setActiveDocument(firstDoc?.name || null) setExpandedDepartments([department.departmentId]) - if ( - domainSlugOrPublicChatbotSlug || - personalChatbotSlugProfileTopicOrThreadSlug || - profileChatbot - ) { - const chatbotEntry = department.chatbots?.find( - (c) => - c?.name && - (c.name.toLowerCase() === profileChatbot || - c.name.toLowerCase() === - personalChatbotSlugProfileTopicOrThreadSlug || - c.name.toLowerCase() === domainSlugOrPublicChatbotSlug), - ) - if (chatbotEntry) { - setActiveChatbot(chatbotEntry) - } else { - setActiveChatbot(null) - } - } else { - setActiveChatbot(null) - } + const chatbotEntry = department.chatbots?.find( + (c) => + c?.name && + (toSlug(c.name) === profileChatbot || + toSlug(c.name) === personalChatbotSlugProfileTopicOrThreadSlug || + toSlug(c.name) === domainSlugOrPublicChatbotSlug), + ) + setActiveChatbot(chatbotEntry || null) } - }, [pathname, departments]) + }, [pathname, departments, isWorkspaceActive, activeThread]) /** * Toggles the selection of a chatbot by its ID. If the chatbot is already selected, it will be removed from the selection. @@ -324,6 +374,7 @@ export function SidebarProvider({ children }: SidebarProviderProps) { } // Wrap setActiveDepartment to accept both tuple and SetStateAction + // biome-ignore lint/correctness/useExhaustiveDependencies: const handleSetActiveDepartment = React.useCallback( (value: React.SetStateAction) => { if (typeof value === 'function') { @@ -333,6 +384,8 @@ export function SidebarProvider({ children }: SidebarProviderProps) { if (newId === null) { setActiveDepartment(null) + setActiveProject(null) + setActiveDocument(null) } else { // Find department name from ID const dept = allDepartments.find((d) => d.departmentId === newId) @@ -344,6 +397,8 @@ export function SidebarProvider({ children }: SidebarProviderProps) { // If value is direct, construct tuple if (value === null) { setActiveDepartment(null) + setActiveProject(null) + setActiveDocument(null) } else { // Find department name from ID const dept = allDepartments.find((d) => d.departmentId === value) diff --git a/apps/pro-web/lib/hooks/use-workspace.tsx b/apps/pro-web/lib/hooks/use-workspace.tsx index 0352a1bcd..99200a2ba 100644 --- a/apps/pro-web/lib/hooks/use-workspace.tsx +++ b/apps/pro-web/lib/hooks/use-workspace.tsx @@ -36,7 +36,7 @@ import type { import { debounce, uniq } from 'lodash' import { appConfig } from 'mb-env' import { useSession } from 'next-auth/react' -import { usePathname } from 'next/navigation' +import { usePathname, useRouter } from 'next/navigation' import type * as React from 'react' import { createContext, @@ -270,56 +270,6 @@ export function WorkspaceProvider({ return combined }, [textDocuments, imageDocuments, spreadsheetDocuments]) - // biome-ignore lint/correctness/useExhaustiveDependencies: - useEffect(() => { - if ( - !departmentFromPathname || - !departmentFromPathname.match(/(general|marketing|product-service)/) - ) { - return - } - - const normalizedDepartmentFromPathname = departmentFromPathname.split('-') - const capitalizedDepartment = normalizedDepartmentFromPathname - .map((chunk) => { - const capitalizedChunk = `${chunk?.slice(0, 1).toUpperCase()}${chunk?.slice(1)}` - return capitalizedChunk - }) - .join('/') - - if (capitalizedDepartment === activeDepartment?.[0]) { - return - } - - const deptProjects = - projectsByDept[capitalizedDepartment as keyof typeof projectsByDept] - const firstDoc = userDocuments.find( - (uDoc) => - uDoc.organization === activeOrganization && - uDoc.project && - deptProjects && - deptProjects[uDoc.project as keyof typeof deptProjects], - ) - - setActiveDepartment([ - capitalizedDepartment, - Number( - DEPARTMENT_NAME_MAP[ - capitalizedDepartment as keyof typeof DEPARTMENT_NAME_MAP - ], - ), - ]) - setActiveProject( - Array.isArray(deptProjects) - ? deptProjects[0] || firstDoc?.project || null - : null, - ) - - if (activeDocument) { - setActiveDocument(firstDoc?.name || null) - } - }, [departmentFromPathname, activeDepartment]) - // Wrapper functions for TanStack mutations + local state management const addOrganization = useCallback( async (name: string, chatbots: OrganizationChatbot[] = []) => { @@ -794,134 +744,6 @@ export function WorkspaceProvider({ ], ) - const routeType = getRouteType(pathname) - const isPro = routeType === 'pro' - - // When organization changes, set department and project if needed - // biome-ignore lint/correctness/useExhaustiveDependencies: - useEffect(() => { - if (!isPro) return - // Skip if no organization selected or departments not loaded - if (!activeOrganization || !orgDepts) { - console.log('Organization or departments not available, skipping effect') - return - } - - console.log('Organization changed to:', activeOrganization) - - // Skip if no departments exist for this organization - if (orgDepts.length === 0) { - console.log('No departments for this organization') - if (activeDepartment !== null) { - setActiveDepartment(null) - } - if (activeProject !== null) { - setActiveProject(null) - } - return - } - - // Check if current department is valid for this organization - const isCurrentDeptValid = - activeDepartment && orgDepts.includes(activeDepartment[0]) // Use tuple index [0] for name - - // Only update if current department is invalid for this organization - if (!isCurrentDeptValid) { - const defaultDeptName = orgDepts[0] - // For now, we default to General department... - const defaultDeptId = 1 // ['General', 1], ['Marketing', 2], ['Product/Service', 3] - - if (!activeDepartment || activeDepartment[0] !== defaultDeptName) { - console.log('Setting default department for new org:', defaultDeptName) - // Non-blocking: Breadcrumb navigation can be deferred - startTransition(() => { - setActiveDepartment([defaultDeptName, defaultDeptId]) - - // Also set first project if available - const orgProjects = projectsByDept[activeOrganization] - if (orgProjects && orgProjects[defaultDeptName]?.length > 0) { - const firstProject = orgProjects[defaultDeptName][0] - console.log('Setting default project for new org:', firstProject) - setActiveProject(firstProject) - } else { - setActiveProject(null) - } - }) - } - } - }, [ - activeOrganization, - orgDepts, - activeDepartment, - activeProject, - projectsByDept, - ]) - - // When department changes, set default project only if current project is invalid - // biome-ignore lint/correctness/useExhaustiveDependencies: - useEffect(() => { - if (!isPro) return - // Skip if any required data is missing - if (!activeDepartment || !activeOrganization || !deptProjects) { - console.log('Department change effect: Missing required data') - return - } - - console.log('Department changed to:', activeDepartment[0]) // Use tuple index [0] for name - - // Handle case where there are no projects for this department - if (deptProjects?.length === 0) { - console.log('No projects for this department') - if (activeProject !== null) { - setActiveProject(null) - } - return - } - - // Check if current project is valid for this department - const isCurrentProjectValid = - activeProject && deptProjects.includes(activeProject) - - // Only update if current project is invalid for this department - if (!isCurrentProjectValid) { - const defaultProject = deptProjects[0] - // Avoid unnecessary state update if project is already set to default - if (activeProject !== defaultProject) { - console.log( - 'Setting default project for new department:', - defaultProject, - ) - // Non-blocking: Breadcrumb navigation can be deferred - startTransition(() => { - setActiveProject(defaultProject) - }) - } - } - }, [activeOrganization, activeDepartment, activeProject, deptProjects]) - - // When project changes, only set default document if there's NO active document - // Do NOT override user's explicit document selection - // biome-ignore lint/correctness/useExhaustiveDependencies: - useEffect(() => { - if (!isPro) return - if (!activeProject || !projectDocs) { - // Skip if no project selected or docs not loaded - return - } - - if (!activeDocument && projectDocs.length > 0) { - // Only auto-select if there's no active document - // This allows users to keep their selected document when switching projects - const defaultDoc = projectDocs[0] - setActiveDocument(defaultDoc) - } - - if (projectDocs.length === 0 && activeDocument !== null) { - // If project has no documents, clear active document - setActiveDocument(null) - } - }, [activeProject, projectDocs]) - return ( {children} diff --git a/apps/pro-web/lib/queries/use-workspace-mutations.ts b/apps/pro-web/lib/queries/use-workspace-mutations.ts index 700d10cf2..3577dbcd2 100644 --- a/apps/pro-web/lib/queries/use-workspace-mutations.ts +++ b/apps/pro-web/lib/queries/use-workspace-mutations.ts @@ -1,5 +1,4 @@ import { getUserOrganizations } from '@/app/actions' -import { DocumentTypeFilterProps } from '@/components/layout/header/document-type-filter' import { DEPARTMENT_ID_MAP, DEPARTMENT_NAME_MAP, diff --git a/apps/pro-web/lib/queries/use-workspace-structure.ts b/apps/pro-web/lib/queries/use-workspace-structure.ts index fee1cecc4..4aa7f7bfa 100644 --- a/apps/pro-web/lib/queries/use-workspace-structure.ts +++ b/apps/pro-web/lib/queries/use-workspace-structure.ts @@ -218,26 +218,23 @@ export function useWorkspaceStructure( ...serverState, updatedAt: serverState.updatedAt || 0, } - // console.log('sourcesToMerge', sourcesToMerge) - const sourceToMerge = sourcesToMerge.reduce((latest, current) => { - return latest && - (latest.updatedAt > current.updatedAt || - latest.documents.length > current.documents.length) - ? latest - : current - }, sourcesToMerge[0]) + + sourcesToMerge.push(finalStructure) + + console.log('sourcesToMerge', sourcesToMerge) + const sourceToMerge = sourcesToMerge.sort( + (a, b) => b.updatedAt - a.updatedAt, + )[0] + console.log('sourceToMerge', sourceToMerge) // console.log('sourceToMerge', sourceToMerge) // Merge source into final structure finalStructure.activeOrganization = sourceToMerge.activeOrganization || finalStructure.organizationList[0] - finalStructure.activeDepartment = sourceToMerge.activeDepartment || [ - 'General', - 1, - ] - finalStructure.activeProject = sourceToMerge.activeProject || null - finalStructure.activeDocument = sourceToMerge.activeDocument || null + finalStructure.activeDepartment = sourceToMerge.activeDepartment + finalStructure.activeProject = sourceToMerge.activeProject + finalStructure.activeDocument = sourceToMerge.activeDocument finalStructure.activeTab = sourceToMerge.activeTab || 'documents' // Merge organizations and their data (IDB/Cache might be ahead of server) @@ -251,23 +248,22 @@ export function useWorkspaceStructure( existingOrgNames.add(orgData.name) } } + finalStructure.organizationList.sort() // Merge departments and projects for all orgs for (const orgName of sourceToMerge.organizationList) { // Merge departments if (sourceToMerge.departmentsByOrg[orgName]) { - if (!finalStructure.departmentsByOrg[orgName]) { - finalStructure.departmentsByOrg[orgName] = - sourceToMerge.departmentsByOrg[orgName] - } else { - const combined = new Set([ - ...finalStructure.departmentsByOrg[orgName], - ...sourceToMerge.departmentsByOrg[orgName], - ]) - finalStructure.departmentsByOrg[orgName] = - Array.from(combined).sort() - } + const combined = new Set([ + ...finalStructure.departmentsByOrg[orgName], + ...sourceToMerge.departmentsByOrg[orgName], + ]) + + finalStructure.departmentsByOrg[orgName] = !finalStructure + .departmentsByOrg[orgName] + ? sourceToMerge.departmentsByOrg[orgName] + : Array.from(combined).sort() } // Merge projects diff --git a/apps/pro-web/lib/threads.ts b/apps/pro-web/lib/threads.ts index 83630963c..14eef329c 100644 --- a/apps/pro-web/lib/threads.ts +++ b/apps/pro-web/lib/threads.ts @@ -74,7 +74,8 @@ export async function getOpeningActiveThreadHelper( // ... you get the idea. 😁 const [ , - _publicCategory, + _publicRootBase, + _publicDepartment, _publicDomain, _publicChatbot, publicThreadSlug, @@ -89,8 +90,7 @@ export async function getOpeningActiveThreadHelper( ] = pathNameParts const [ , - _personalRootBase, - _personalCategory, + _personalDepartment, _personalDomain, _personalChatbot, personalThreadSlug, @@ -101,7 +101,7 @@ export async function getOpeningActiveThreadHelper( _userProfileRootBase, _userProfileSlug, _userProfileThreadRootBase, - _userProfileCategory, + _userProfileDepartment, _userProfileDomain, _userProfileChatbot, userProfileThreadSlug, From 133cb74f242cdba6a4f59f850cf29acf4802d86b Mon Sep 17 00:00:00 2001 From: Roberto 'Andler' Lucas Date: Thu, 4 Dec 2025 17:45:24 -0600 Subject: [PATCH 04/22] fix: workspace active navigation persistent state --- .../components/layout/header/header.tsx | 38 +- .../workspace-document-breadcrumb.tsx | 349 +++------------- .../routes/workspace/workspace-breadcrumb.tsx | 371 ++++++++++++++++++ apps/pro-web/lib/constants/workspace.ts | 6 + apps/pro-web/lib/hooks/use-sidebar.tsx | 78 ++-- apps/pro-web/lib/hooks/use-workspace.tsx | 83 +--- .../lib/queries/use-workspace-mutations.ts | 3 +- .../lib/queries/use-workspace-structure.ts | 2 +- 8 files changed, 462 insertions(+), 468 deletions(-) create mode 100644 apps/pro-web/components/routes/workspace/workspace-breadcrumb.tsx diff --git a/apps/pro-web/components/layout/header/header.tsx b/apps/pro-web/components/layout/header/header.tsx index 23b8949bc..d7b28f0d5 100644 --- a/apps/pro-web/components/layout/header/header.tsx +++ b/apps/pro-web/components/layout/header/header.tsx @@ -17,6 +17,7 @@ import { useThread } from '@/lib/hooks/use-thread' import { useWorkspace } from '@/lib/hooks/use-workspace' import { cn, getRouteColor, getRouteType } from '@/lib/utils' +import { WorkspaceBreadcrumb } from '@/components/routes/workspace/workspace-breadcrumb' import { Separator } from '@/components/ui/separator' import type { WorkspaceDocumentType } from '@/types/thread.types' import { uniq } from 'lodash' @@ -250,24 +251,7 @@ export function Header() { className="bg-muted-foreground/50 h-6" /> {/* Workspace Breadcrumbs */} -
    - { - if (v === activeOrganization) return - setActiveOrganization(v === 'None' ? null : v) - setActiveDepartment(null) - setActiveProject(null) - setActiveDocument(null) - if (isOpenPopup) setIsOpenPopup(false) - setActiveThread(null) - }} - addType="organization" - onNewItem={handleAddEntity} - /> -
    + {/* User login - Always show on mobile */} @@ -285,24 +269,6 @@ export function Header() { onClose={() => setIsEntityDialogOpen(false)} onConfirm={handleCreateEntity} /> - - {/* Pro Onboarding for Organization Creation */} - setIsOnboardingOpen(false)} - /> - - {/* Document Creation Dialog */} - ) } diff --git a/apps/pro-web/components/routes/workspace/document-tab/workspace-document-breadcrumb.tsx b/apps/pro-web/components/routes/workspace/document-tab/workspace-document-breadcrumb.tsx index ee9f3c879..1db8e7c70 100644 --- a/apps/pro-web/components/routes/workspace/document-tab/workspace-document-breadcrumb.tsx +++ b/apps/pro-web/components/routes/workspace/document-tab/workspace-document-breadcrumb.tsx @@ -1,39 +1,28 @@ 'use client' -import { getThreadBySlug } from '@/app/actions' import { CreateDocumentAlert } from '@/components/layout/header/create-document-alert' -import { CreateEntityAlert } from '@/components/layout/header/create-entity-alert' -import { Crumb, DocumentCrumb } from '@/components/layout/header/crumb-header' -import { DocumentTypeFilter } from '@/components/layout/header/document-type-filter' +import { DocumentCrumb } from '@/components/layout/header/crumb-header' import { AlertDialogue } from '@/components/shared/alert-dialogue' import { Separator } from '@/components/ui/separator' import { - ALERT_DIALOGUE_DEPARTMENT_KEY, ALERT_DIALOGUE_DOCUMENT_KEY, ALERT_DIALOGUE_KEY, - ALERT_DIALOGUE_PROJECT_KEY, } from '@/lib/constants/config' +import { useSidebar } from '@/lib/hooks/use-sidebar' import { useThread } from '@/lib/hooks/use-thread' import { useWorkspace } from '@/lib/hooks/use-workspace' -import { - useAddDocument, - useAddProject, - useSetActiveNavigation, - useUserId, -} from '@/lib/queries' -import { getCanonicalDomain, urlBuilders } from '@/lib/url' +import { useAddDocument, useUserId } from '@/lib/queries' +import { getCanonicalDomain } from '@/lib/url' import { getThread } from '@/services/hasura' import type { WorkspaceDocumentMetadata, WorkspaceDocumentType, - WorkspaceDocumentTypeFilter, } from '@/types/thread.types' -import { toSlug, useLocalStorage } from 'mb-lib' -import { useRouter } from 'next/navigation' -import { startTransition, useMemo, useState } from 'react' +import { useLocalStorage } from 'mb-lib' +import { useMemo, useState } from 'react' export function WorkspaceDocumentBreadcrumb() { - const router = useRouter() + const { navigateTo } = useSidebar() const { activeThread, setActiveThread, setIsOpenPopup } = useThread() const workspaceContext = useWorkspace() const { @@ -41,46 +30,22 @@ export function WorkspaceDocumentBreadcrumb() { activeDepartment, activeProject, activeDocument, - departmentList, projectsByDept, activeDocumentType, userDocuments, setActiveDocumentType, - setActiveDepartment, setActiveProject, setActiveDocument, } = workspaceContext - const [isChangeDeptDialogOpen, setIsChangeDeptDialogOpen] = useState(false) - const [isChangeProjectDialogOpen, setIsChangeProjectDialogOpen] = - useState(false) const [isChangeDocumentDialogOpen, setIsChangeDocumentDialogOpen] = useState(false) - const [isProjectDialogOpen, setIsProjectDialogOpen] = useState(false) - const [pendingDepartment, setPendingDepartment] = useState< - [string, number] | null - >(null) - const [pendingProject, setPendingProject] = useState(null) + const [pendingDocument, setPendingDocument] = useState(null) const [isProcessing, setIsProcessing] = useState(false) const [isDocumentDialogOpen, setIsDocumentDialogOpen] = useState(false) const [documentName, setDocumentName] = useState('Untitled Document') - // Department options from current organization - const deptOptions = useMemo(() => { - if (!activeOrganization || !departmentList) return [] - return departmentList[activeOrganization] || [] - }, [activeOrganization, departmentList]) - - // Project options from current department - const projectOptions = useMemo(() => { - if (!activeOrganization || !activeDepartment || !projectsByDept) return [] - const deptProjects = - projectsByDept[activeOrganization]?.[activeDepartment[0]] - - return !deptProjects || deptProjects.length === 0 ? [] : deptProjects - }, [activeOrganization, activeDepartment, projectsByDept]) - // Build document options with thread documents const { documentOptions, threadDocsByName } = useMemo(() => { const dedupe = (arr: string[]) => Array.from(new Set(arr)) @@ -116,163 +81,6 @@ export function WorkspaceDocumentBreadcrumb() { {}, ) - // Handle department change - const handleDepartmentSelect = (deptName: string) => { - // Find department ID from the name - const deptId = deptOptions.findIndex((d) => d === deptName) + 1 - - if ( - !hiddenAlerts[ALERT_DIALOGUE_DEPARTMENT_KEY] && - activeThread && - deptId !== activeDepartment?.[1] - ) { - // Show dialog if in active thread - setPendingDepartment([deptName, deptId]) - setIsChangeDeptDialogOpen(true) - } else { - // Directly change if not in thread - changeDepartment([deptName, deptId]) - } - } - - const changeDepartment = async (dept: [string, number]) => { - setActiveDepartment(dept) - - // Get first project in new department if any - const newProjects = - projectsByDept[activeOrganization || '']?.[dept[0]] || [] - const firstProject = newProjects.length > 0 ? newProjects[0] : null - - setActiveProject(firstProject || null) - setActiveDocument(null) - - // Find the first matching document (text type) for the new org/dept/project - let firstDoc = null - if (activeOrganization && dept[0] && firstProject) { - firstDoc = userDocuments.find( - (doc) => - doc.organization === activeOrganization && - doc.department === dept[0] && - doc.project === firstProject && - doc.type === 'text', - ) - } - - // If found, try to get threadSlug and route to chatbot thread if possible - if (firstDoc?.threadSlug && activeOrganization) { - // Use a valid type for chatbotThreadListUrl (e.g., 'pro') - const threadData = await getThreadBySlug(firstDoc.threadSlug) - // console.log('threadData response', threadData) - const domain = getCanonicalDomain(threadData.chatbot?.name || 'ai') - const url = urlBuilders.chatbotThreadListUrl({ - type: 'pro', - category: dept[0], - domain, - chatbot: threadData.chatbot?.name || '', - threadSlug: firstDoc.threadSlug, - }) - router.push(url) - return - } - - // Fallback: Map department name to URL slug and route to department - const deptSlug = toSlug(dept[0]) - router.push(urlBuilders.departmentUrl(deptSlug)) - } - - const handleConfirmDepartmentChange = ( - e: React.MouseEvent, - ) => { - e.stopPropagation() - setIsProcessing(true) - - try { - if (pendingDepartment) { - // Exit thread workspace mode - setIsOpenPopup(false) - setActiveThread(null) - - // Change department - changeDepartment(pendingDepartment) - } - } finally { - setIsProcessing(false) - setIsChangeDeptDialogOpen(false) - setPendingDepartment(null) - } - } - - const handleProjectSelect = (project: string) => { - if ( - !hiddenAlerts[ALERT_DIALOGUE_PROJECT_KEY] && - activeThread && - project !== activeProject - ) { - // Show dialog if in active thread and changing project - setPendingProject(project) - setIsChangeProjectDialogOpen(true) - } else { - // Directly change if not in thread - changeProject(project) - } - } - - const changeProject = async (project: string) => { - setActiveProject(project) - setActiveDocument(null) - - // Find the first matching document (text type) for the new org/dept/project - let firstDoc = null - if (activeOrganization && activeDepartment && project) { - firstDoc = userDocuments.find( - (doc) => - doc.organization === activeOrganization && - doc.department === activeDepartment[0] && - doc.project === project && - doc.type === 'text', - ) - } - - // If found, try to get threadSlug and route to chatbot thread if possible - if (firstDoc?.threadSlug && activeDepartment && activeOrganization) { - // Use a valid type for chatbotThreadListUrl (e.g., 'pro') - const threadData = await getThreadBySlug(firstDoc.threadSlug) - // console.log('threadData response', threadData) - const domain = getCanonicalDomain(threadData.chatbot?.name || 'ai') - const url = urlBuilders.chatbotThreadListUrl({ - type: 'pro', - category: activeDepartment[0], - domain, - chatbot: threadData.chatbot?.name || '', - threadSlug: firstDoc.threadSlug, - }) - router.push(url) - return - } - } - - const handleConfirmProjectChange = ( - e: React.MouseEvent, - ) => { - e.stopPropagation() - setIsProcessing(true) - - try { - if (pendingProject) { - // Exit thread workspace mode - setIsOpenPopup(false) - setActiveThread(null) - - // Change project - changeProject(pendingProject) - } - } finally { - setIsProcessing(false) - setIsChangeProjectDialogOpen(false) - setPendingProject(null) - } - } - const handleDocumentSelect = (doc: string) => { const documentThread = threadDocsByName.get(doc) @@ -289,7 +97,7 @@ export function WorkspaceDocumentBreadcrumb() { setIsChangeDocumentDialogOpen(true) } else { // Directly change if not in thread - setActiveDocument(doc) + changeDocument(doc) } } @@ -300,34 +108,7 @@ export function WorkspaceDocumentBreadcrumb() { setIsProcessing(true) try { if (pendingDocument) { - const documentThread = threadDocsByName.get(pendingDocument) - console.log('pendingDocument', pendingDocument) - console.log('documentThread', documentThread) - const threadData = documentThread?.threadSlug - ? await getThread({ - threadSlug: documentThread.threadSlug, - isPersonal: true, - }) - : null - - if (threadData) { - const url = urlBuilders.threadUrl({ - type: 'pro', - category: documentThread?.department || '', - domain: getCanonicalDomain(threadData.chatbot.name), - chatbot: threadData.chatbot.name, - threadSlug: threadData.slug, - }) - router.push(url) - } - - requestAnimationFrame(() => { - // Exit thread workspace mode - setActiveThread(threadData) - setIsOpenPopup(Boolean(threadData)) - // Change document - setActiveDocument(pendingDocument) - }) + changeDocument(pendingDocument) } } finally { setIsProcessing(false) @@ -336,34 +117,49 @@ export function WorkspaceDocumentBreadcrumb() { } } - const onNewItem = (type: 'organization' | 'project' | 'document') => { - if (type === 'project') { - setIsProjectDialogOpen(true) + const changeDocument = async (docName: string) => { + const documentThread = threadDocsByName.get(docName) + console.log('pendingDocument', docName) + console.log('documentThread', documentThread) + const threadData = documentThread?.threadSlug + ? await getThread({ + threadSlug: documentThread.threadSlug, + isPersonal: true, + }) + : null + + if (threadData) { + navigateTo({ + urlType: 'threadUrl', + navigationParams: { + type: 'pro', + category: documentThread?.department || '', + domain: getCanonicalDomain(threadData.chatbot.name), + chatbot: threadData.chatbot.name, + threadSlug: threadData.slug, + }, + shallow: true, + }) } + + requestAnimationFrame(() => { + // Exit thread workspace mode + setActiveThread(threadData) + setIsOpenPopup(Boolean(threadData)) + // Change document + setActiveDocument(docName) + }) + } + + const onNewItem = (type: 'organization' | 'project' | 'document') => { if (type === 'document') { setIsDocumentDialogOpen(true) } } const userId = useUserId() - const addProjectMutation = useAddProject(userId) const addDocumentMutation = useAddDocument(userId) - const handleCreateProject = (name: string) => { - if (!activeOrganization || !activeDepartment) return - - addProjectMutation.mutate({ - org: activeOrganization, - dept: activeDepartment[0], - name, - }) - setActiveProject(name) - setIsProjectDialogOpen(false) - setIsOpenPopup(false) - setActiveThread(null) - setActiveDocument(null) - } - const handleCreateDocument = ( name: string, documentType: WorkspaceDocumentType, @@ -407,30 +203,6 @@ export function WorkspaceDocumentBreadcrumb() { return (