From 43537c5e20bf45ee6b11997618e78a3b6e3f587d Mon Sep 17 00:00:00 2001 From: Arjun <6592213+arkml@users.noreply.github.com> Date: Tue, 19 May 2026 18:24:48 +0530 Subject: [PATCH] view more in knowledge --- apps/x/apps/renderer/src/App.tsx | 205 ++++-- .../src/components/html-file-viewer.tsx | 58 +- .../src/components/knowledge-view.tsx | 405 ++++++++++++ .../src/components/sidebar-content.tsx | 610 +++--------------- 4 files changed, 656 insertions(+), 622 deletions(-) create mode 100644 apps/x/apps/renderer/src/components/knowledge-view.tsx diff --git a/apps/x/apps/renderer/src/App.tsx b/apps/x/apps/renderer/src/App.tsx index 558e19e3..1544306c 100644 --- a/apps/x/apps/renderer/src/App.tsx +++ b/apps/x/apps/renderer/src/App.tsx @@ -26,6 +26,7 @@ import { LiveNotesView } from '@/components/live-notes-view'; import { BgTasksView } from '@/components/bg-tasks-view'; import { EmailView } from '@/components/email-view'; import { WorkspaceView } from '@/components/workspace-view'; +import { KnowledgeView } from '@/components/knowledge-view'; import { MeetingsView } from '@/components/meetings-view'; import { SidebarSectionProvider } from '@/contexts/sidebar-context'; import { @@ -185,6 +186,7 @@ const BG_TASKS_TAB_PATH = '__rowboat_bg_tasks__' const EMAIL_TAB_PATH = '__rowboat_email__' const WORKSPACE_TAB_PATH = '__rowboat_workspace__' const WORKSPACE_ROOT = 'knowledge/Workspace' +const KNOWLEDGE_VIEW_TAB_PATH = '__rowboat_knowledge_view__' const BASES_DEFAULT_TAB_PATH = '__rowboat_bases_default__' const clampNumber = (value: number, min: number, max: number) => @@ -319,6 +321,7 @@ const isLiveNotesTabPath = (path: string) => path === LIVE_NOTES_TAB_PATH const isBgTasksTabPath = (path: string) => path === BG_TASKS_TAB_PATH const isEmailTabPath = (path: string) => path === EMAIL_TAB_PATH const isWorkspaceTabPath = (path: string) => path === WORKSPACE_TAB_PATH +const isKnowledgeViewTabPath = (path: string) => path === KNOWLEDGE_VIEW_TAB_PATH const isBaseFilePath = (path: string) => path.endsWith('.base') || path === BASES_DEFAULT_TAB_PATH const getSuggestedTopicTargetFolder = (category?: string) => { @@ -570,6 +573,7 @@ type ViewState = | { type: 'live-notes' } | { type: 'email' } | { type: 'workspace'; path?: string } + | { type: 'knowledge-view' } function viewStatesEqual(a: ViewState, b: ViewState): boolean { if (a.type !== b.type) return false @@ -624,6 +628,8 @@ function parseDeepLink(input: string): ViewState | null { const path = params.get('path') return { type: 'workspace', path: path ?? undefined } } + case 'knowledge-view': + return { type: 'knowledge-view' } default: return null } @@ -761,6 +767,7 @@ function App() { const [isEmailOpen, setIsEmailOpen] = useState(false) const [isWorkspaceOpen, setIsWorkspaceOpen] = useState(false) const [workspaceInitialPath, setWorkspaceInitialPath] = useState(null) + const [isKnowledgeViewOpen, setIsKnowledgeViewOpen] = useState(false) const [expandedFrom, setExpandedFrom] = useState<{ path: string | null graph: boolean @@ -1098,6 +1105,7 @@ function App() { if (isBgTasksTabPath(tab.path)) return 'Background tasks' if (isEmailTabPath(tab.path)) return 'Email' if (isWorkspaceTabPath(tab.path)) return 'Workspace' + if (isKnowledgeViewTabPath(tab.path)) return 'Knowledge' if (tab.path === BASES_DEFAULT_TAB_PATH) return 'Bases' if (tab.path.endsWith('.base')) return tab.path.split('/').pop()?.replace(/\.base$/i, '') || 'Base' return tab.path.split('/').pop()?.replace(/\.md$/i, '') || tab.path @@ -2810,7 +2818,7 @@ function App() { setActiveFileTabId(existingTab.id) setIsGraphOpen(false) setIsSuggestedTopicsOpen(false) - setIsMeetingsOpen(false); setIsLiveNotesOpen(false); setIsBgTasksOpen(false); setIsEmailOpen(false); setIsWorkspaceOpen(false) + setIsMeetingsOpen(false); setIsLiveNotesOpen(false); setIsBgTasksOpen(false); setIsEmailOpen(false); setIsWorkspaceOpen(false); setIsKnowledgeViewOpen(false) setSelectedPath(path) return } @@ -2819,7 +2827,7 @@ function App() { setActiveFileTabId(id) setIsGraphOpen(false) setIsSuggestedTopicsOpen(false) - setIsMeetingsOpen(false); setIsLiveNotesOpen(false); setIsBgTasksOpen(false); setIsEmailOpen(false); setIsWorkspaceOpen(false) + setIsMeetingsOpen(false); setIsLiveNotesOpen(false); setIsBgTasksOpen(false); setIsEmailOpen(false); setIsWorkspaceOpen(false); setIsKnowledgeViewOpen(false) setSelectedPath(path) }, [fileTabs, dismissBrowserOverlay]) @@ -2838,14 +2846,14 @@ function App() { setSelectedPath(null) setIsGraphOpen(true) setIsSuggestedTopicsOpen(false) - setIsMeetingsOpen(false); setIsLiveNotesOpen(false); setIsBgTasksOpen(false); setIsEmailOpen(false); setIsWorkspaceOpen(false) + setIsMeetingsOpen(false); setIsLiveNotesOpen(false); setIsBgTasksOpen(false); setIsEmailOpen(false); setIsWorkspaceOpen(false); setIsKnowledgeViewOpen(false) return } if (isSuggestedTopicsTabPath(tab.path)) { setSelectedPath(null) setIsGraphOpen(false) setIsSuggestedTopicsOpen(true) - setIsMeetingsOpen(false); setIsLiveNotesOpen(false); setIsBgTasksOpen(false); setIsEmailOpen(false); setIsWorkspaceOpen(false) + setIsMeetingsOpen(false); setIsLiveNotesOpen(false); setIsBgTasksOpen(false); setIsEmailOpen(false); setIsWorkspaceOpen(false); setIsKnowledgeViewOpen(false) return } if (isLiveNotesTabPath(tab.path)) { @@ -2856,6 +2864,7 @@ function App() { setIsBgTasksOpen(false) setIsEmailOpen(false) setIsWorkspaceOpen(false) + setIsKnowledgeViewOpen(false) setIsLiveNotesOpen(true) return } @@ -2867,6 +2876,7 @@ function App() { setIsLiveNotesOpen(false) setIsEmailOpen(false) setIsWorkspaceOpen(false) + setIsKnowledgeViewOpen(false) setIsBgTasksOpen(true) return } @@ -2879,6 +2889,7 @@ function App() { setIsBgTasksOpen(false) setIsEmailOpen(false) setIsWorkspaceOpen(false) + setIsKnowledgeViewOpen(false) return } if (isEmailTabPath(tab.path)) { @@ -2889,6 +2900,7 @@ function App() { setIsLiveNotesOpen(false) setIsBgTasksOpen(false) setIsWorkspaceOpen(false) + setIsKnowledgeViewOpen(false) setIsEmailOpen(true) return } @@ -2900,18 +2912,31 @@ function App() { setIsLiveNotesOpen(false) setIsBgTasksOpen(false) setIsEmailOpen(false) + setIsKnowledgeViewOpen(false) setIsWorkspaceOpen(true) return } + if (isKnowledgeViewTabPath(tab.path)) { + setSelectedPath(null) + setIsGraphOpen(false) + setIsSuggestedTopicsOpen(false) + setIsMeetingsOpen(false) + setIsLiveNotesOpen(false) + setIsBgTasksOpen(false) + setIsEmailOpen(false) + setIsWorkspaceOpen(false) + setIsKnowledgeViewOpen(true) + return + } setIsGraphOpen(false) setIsSuggestedTopicsOpen(false) - setIsMeetingsOpen(false); setIsLiveNotesOpen(false); setIsBgTasksOpen(false); setIsEmailOpen(false); setIsWorkspaceOpen(false) + setIsMeetingsOpen(false); setIsLiveNotesOpen(false); setIsBgTasksOpen(false); setIsEmailOpen(false); setIsWorkspaceOpen(false); setIsKnowledgeViewOpen(false) setSelectedPath(tab.path) }, [fileTabs, isRightPaneMaximized, dismissBrowserOverlay]) const closeFileTab = useCallback((tabId: string) => { const closingTab = fileTabs.find(t => t.id === tabId) - if (closingTab && !isGraphTabPath(closingTab.path) && !isSuggestedTopicsTabPath(closingTab.path) && !isLiveNotesTabPath(closingTab.path) && !isBgTasksTabPath(closingTab.path) && !isEmailTabPath(closingTab.path) && !isWorkspaceTabPath(closingTab.path) && !isBaseFilePath(closingTab.path)) { + if (closingTab && !isGraphTabPath(closingTab.path) && !isSuggestedTopicsTabPath(closingTab.path) && !isLiveNotesTabPath(closingTab.path) && !isBgTasksTabPath(closingTab.path) && !isEmailTabPath(closingTab.path) && !isWorkspaceTabPath(closingTab.path) && !isKnowledgeViewTabPath(closingTab.path) && !isBaseFilePath(closingTab.path)) { removeEditorCacheForPath(closingTab.path) initialContentByPathRef.current.delete(closingTab.path) untitledRenameReadyPathsRef.current.delete(closingTab.path) @@ -2934,7 +2959,7 @@ function App() { setSelectedPath(null) setIsGraphOpen(false) setIsSuggestedTopicsOpen(false) - setIsMeetingsOpen(false); setIsLiveNotesOpen(false); setIsBgTasksOpen(false); setIsEmailOpen(false); setIsWorkspaceOpen(false) + setIsMeetingsOpen(false); setIsLiveNotesOpen(false); setIsBgTasksOpen(false); setIsEmailOpen(false); setIsWorkspaceOpen(false); setIsKnowledgeViewOpen(false) return [] } const idx = prev.findIndex(t => t.id === tabId) @@ -2948,12 +2973,12 @@ function App() { setSelectedPath(null) setIsGraphOpen(true) setIsSuggestedTopicsOpen(false) - setIsMeetingsOpen(false); setIsLiveNotesOpen(false); setIsBgTasksOpen(false); setIsEmailOpen(false); setIsWorkspaceOpen(false) + setIsMeetingsOpen(false); setIsLiveNotesOpen(false); setIsBgTasksOpen(false); setIsEmailOpen(false); setIsWorkspaceOpen(false); setIsKnowledgeViewOpen(false) } else if (isSuggestedTopicsTabPath(newActiveTab.path)) { setSelectedPath(null) setIsGraphOpen(false) setIsSuggestedTopicsOpen(true) - setIsMeetingsOpen(false); setIsLiveNotesOpen(false); setIsBgTasksOpen(false); setIsEmailOpen(false); setIsWorkspaceOpen(false) + setIsMeetingsOpen(false); setIsLiveNotesOpen(false); setIsBgTasksOpen(false); setIsEmailOpen(false); setIsWorkspaceOpen(false); setIsKnowledgeViewOpen(false) } else if (isMeetingsTabPath(newActiveTab.path)) { setSelectedPath(null) setIsGraphOpen(false) @@ -2963,6 +2988,7 @@ function App() { setIsBgTasksOpen(false) setIsEmailOpen(false) setIsWorkspaceOpen(false) + setIsKnowledgeViewOpen(false) } else if (isLiveNotesTabPath(newActiveTab.path)) { setSelectedPath(null) setIsGraphOpen(false) @@ -2971,6 +2997,7 @@ function App() { setIsBgTasksOpen(false) setIsEmailOpen(false) setIsWorkspaceOpen(false) + setIsKnowledgeViewOpen(false) setIsLiveNotesOpen(true) } else if (isBgTasksTabPath(newActiveTab.path)) { setSelectedPath(null) @@ -2981,6 +3008,7 @@ function App() { setIsBgTasksOpen(true) setIsEmailOpen(false) setIsWorkspaceOpen(false) + setIsKnowledgeViewOpen(false) } else if (isEmailTabPath(newActiveTab.path)) { setSelectedPath(null) setIsGraphOpen(false) @@ -2989,6 +3017,7 @@ function App() { setIsLiveNotesOpen(false) setIsBgTasksOpen(false) setIsWorkspaceOpen(false) + setIsKnowledgeViewOpen(false) setIsEmailOpen(true) } else if (isWorkspaceTabPath(newActiveTab.path)) { setSelectedPath(null) @@ -2998,11 +3027,22 @@ function App() { setIsLiveNotesOpen(false) setIsBgTasksOpen(false) setIsEmailOpen(false) + setIsKnowledgeViewOpen(false) setIsWorkspaceOpen(true) + } else if (isKnowledgeViewTabPath(newActiveTab.path)) { + setSelectedPath(null) + setIsGraphOpen(false) + setIsSuggestedTopicsOpen(false) + setIsMeetingsOpen(false) + setIsLiveNotesOpen(false) + setIsBgTasksOpen(false) + setIsEmailOpen(false) + setIsWorkspaceOpen(false) + setIsKnowledgeViewOpen(true) } else { setIsGraphOpen(false) setIsSuggestedTopicsOpen(false) - setIsMeetingsOpen(false); setIsLiveNotesOpen(false); setIsBgTasksOpen(false); setIsEmailOpen(false); setIsWorkspaceOpen(false) + setIsMeetingsOpen(false); setIsLiveNotesOpen(false); setIsBgTasksOpen(false); setIsEmailOpen(false); setIsWorkspaceOpen(false); setIsKnowledgeViewOpen(false) setSelectedPath(newActiveTab.path) } } @@ -3033,7 +3073,7 @@ function App() { dismissBrowserOverlay() handleNewChat() // Left-pane "new chat" should always open full chat view. - if (selectedPath || isGraphOpen || isSuggestedTopicsOpen || isMeetingsOpen || isLiveNotesOpen || isBgTasksOpen || isEmailOpen || isWorkspaceOpen) { + if (selectedPath || isGraphOpen || isSuggestedTopicsOpen || isMeetingsOpen || isLiveNotesOpen || isBgTasksOpen || isEmailOpen || isWorkspaceOpen || isKnowledgeViewOpen) { setExpandedFrom({ path: selectedPath, graph: isGraphOpen, @@ -3050,8 +3090,8 @@ function App() { setSelectedPath(null) setIsGraphOpen(false) setIsSuggestedTopicsOpen(false) - setIsMeetingsOpen(false); setIsLiveNotesOpen(false); setIsBgTasksOpen(false); setIsEmailOpen(false); setIsWorkspaceOpen(false) - }, [chatTabs, activeChatTabId, dismissBrowserOverlay, handleNewChat, selectedPath, isGraphOpen, isSuggestedTopicsOpen, isMeetingsOpen, isLiveNotesOpen, isBgTasksOpen, isEmailOpen, isWorkspaceOpen]) + setIsMeetingsOpen(false); setIsLiveNotesOpen(false); setIsBgTasksOpen(false); setIsEmailOpen(false); setIsWorkspaceOpen(false); setIsKnowledgeViewOpen(false) + }, [chatTabs, activeChatTabId, dismissBrowserOverlay, handleNewChat, selectedPath, isGraphOpen, isSuggestedTopicsOpen, isMeetingsOpen, isLiveNotesOpen, isBgTasksOpen, isEmailOpen, isWorkspaceOpen, isKnowledgeViewOpen]) // Sidebar variant: create/switch chat tab without leaving file/graph context. const handleNewChatTabInSidebar = useCallback(() => { @@ -3183,7 +3223,7 @@ function App() { const handleOpenFullScreenChat = useCallback(() => { // Remember where we came from so the close button can return - if (selectedPath || isGraphOpen || isSuggestedTopicsOpen || isMeetingsOpen || isLiveNotesOpen || isBgTasksOpen || isEmailOpen || isWorkspaceOpen) { + if (selectedPath || isGraphOpen || isSuggestedTopicsOpen || isMeetingsOpen || isLiveNotesOpen || isBgTasksOpen || isEmailOpen || isWorkspaceOpen || isKnowledgeViewOpen) { setExpandedFrom({ path: selectedPath, graph: isGraphOpen, @@ -3199,19 +3239,19 @@ function App() { setSelectedPath(null) setIsGraphOpen(false) setIsSuggestedTopicsOpen(false) - setIsMeetingsOpen(false); setIsLiveNotesOpen(false); setIsBgTasksOpen(false); setIsEmailOpen(false); setIsWorkspaceOpen(false) - }, [selectedPath, isGraphOpen, isSuggestedTopicsOpen, isMeetingsOpen, isLiveNotesOpen, isBgTasksOpen, isEmailOpen, isWorkspaceOpen, dismissBrowserOverlay]) + setIsMeetingsOpen(false); setIsLiveNotesOpen(false); setIsBgTasksOpen(false); setIsEmailOpen(false); setIsWorkspaceOpen(false); setIsKnowledgeViewOpen(false) + }, [selectedPath, isGraphOpen, isSuggestedTopicsOpen, isMeetingsOpen, isLiveNotesOpen, isBgTasksOpen, isEmailOpen, isWorkspaceOpen, isKnowledgeViewOpen, dismissBrowserOverlay]) const handleCloseFullScreenChat = useCallback(() => { if (expandedFrom) { if (expandedFrom.graph) { setIsGraphOpen(true) setIsSuggestedTopicsOpen(false) - setIsMeetingsOpen(false); setIsLiveNotesOpen(false); setIsBgTasksOpen(false); setIsEmailOpen(false); setIsWorkspaceOpen(false) + setIsMeetingsOpen(false); setIsLiveNotesOpen(false); setIsBgTasksOpen(false); setIsEmailOpen(false); setIsWorkspaceOpen(false); setIsKnowledgeViewOpen(false) } else if (expandedFrom.suggestedTopics) { setIsGraphOpen(false) setIsSuggestedTopicsOpen(true) - setIsMeetingsOpen(false); setIsLiveNotesOpen(false); setIsBgTasksOpen(false); setIsEmailOpen(false); setIsWorkspaceOpen(false) + setIsMeetingsOpen(false); setIsLiveNotesOpen(false); setIsBgTasksOpen(false); setIsEmailOpen(false); setIsWorkspaceOpen(false); setIsKnowledgeViewOpen(false) } else if (expandedFrom.meetings) { setIsGraphOpen(false) setIsSuggestedTopicsOpen(false) @@ -3243,7 +3283,7 @@ function App() { } else if (expandedFrom.path) { setIsGraphOpen(false) setIsSuggestedTopicsOpen(false) - setIsMeetingsOpen(false); setIsLiveNotesOpen(false); setIsBgTasksOpen(false); setIsEmailOpen(false); setIsWorkspaceOpen(false) + setIsMeetingsOpen(false); setIsLiveNotesOpen(false); setIsBgTasksOpen(false); setIsEmailOpen(false); setIsWorkspaceOpen(false); setIsKnowledgeViewOpen(false) setSelectedPath(expandedFrom.path) } setExpandedFrom(null) @@ -3258,10 +3298,11 @@ function App() { if (isLiveNotesOpen) return { type: 'live-notes' } if (isSuggestedTopicsOpen) return { type: 'suggested-topics' } if (isWorkspaceOpen) return { type: 'workspace', path: workspaceInitialPath ?? undefined } + if (isKnowledgeViewOpen) return { type: 'knowledge-view' } if (selectedPath) return { type: 'file', path: selectedPath } if (isGraphOpen) return { type: 'graph' } return { type: 'chat', runId } - }, [selectedBackgroundTask, isEmailOpen, isMeetingsOpen, isLiveNotesOpen, isBgTasksOpen, isSuggestedTopicsOpen, selectedPath, isGraphOpen, isWorkspaceOpen, workspaceInitialPath, runId]) + }, [selectedBackgroundTask, isEmailOpen, isMeetingsOpen, isLiveNotesOpen, isBgTasksOpen, isSuggestedTopicsOpen, selectedPath, isGraphOpen, isWorkspaceOpen, isKnowledgeViewOpen, workspaceInitialPath, runId]) const appendUnique = useCallback((stack: ViewState[], entry: ViewState) => { const last = stack[stack.length - 1] @@ -3373,6 +3414,17 @@ function App() { setActiveFileTabId(id) }, [fileTabs]) + const ensureKnowledgeViewFileTab = useCallback(() => { + const existing = fileTabs.find((tab) => isKnowledgeViewTabPath(tab.path)) + if (existing) { + setActiveFileTabId(existing.id) + return + } + const id = newFileTabId() + setFileTabs((prev) => [...prev, { id, path: KNOWLEDGE_VIEW_TAB_PATH }]) + setActiveFileTabId(id) + }, [fileTabs]) + const openEmailView = useCallback(() => { setSelectedPath(null) setIsGraphOpen(false) @@ -3382,6 +3434,7 @@ function App() { setIsLiveNotesOpen(false) setIsBgTasksOpen(false) setIsWorkspaceOpen(false) + setIsKnowledgeViewOpen(false) setSelectedBackgroundTask(null) setExpandedFrom(null) setIsRightPaneMaximized(false) @@ -3394,7 +3447,7 @@ function App() { setIsGraphOpen(false) setIsBrowserOpen(false) setIsSuggestedTopicsOpen(false) - setIsMeetingsOpen(false); setIsLiveNotesOpen(false); setIsBgTasksOpen(false); setIsEmailOpen(false); setIsWorkspaceOpen(false) + setIsMeetingsOpen(false); setIsLiveNotesOpen(false); setIsBgTasksOpen(false); setIsEmailOpen(false); setIsWorkspaceOpen(false); setIsKnowledgeViewOpen(false) setSelectedBackgroundTask(null) setExpandedFrom(null) setIsRightPaneMaximized(false) @@ -3412,6 +3465,7 @@ function App() { setIsBgTasksOpen(false) setIsEmailOpen(false) setIsWorkspaceOpen(false) + setIsKnowledgeViewOpen(false) setSelectedBackgroundTask(null) setExpandedFrom(null) setIsRightPaneMaximized(false) @@ -3427,7 +3481,7 @@ function App() { // visible in the middle pane. setIsBrowserOpen(false) setIsSuggestedTopicsOpen(false) - setIsMeetingsOpen(false); setIsLiveNotesOpen(false); setIsBgTasksOpen(false); setIsEmailOpen(false); setIsWorkspaceOpen(false) + setIsMeetingsOpen(false); setIsLiveNotesOpen(false); setIsBgTasksOpen(false); setIsEmailOpen(false); setIsWorkspaceOpen(false); setIsKnowledgeViewOpen(false) setExpandedFrom(null) // Preserve split vs knowledge-max mode when navigating knowledge files. // Only exit chat-only maximize, because that would hide the selected file. @@ -3442,7 +3496,7 @@ function App() { setSelectedPath(null) setIsBrowserOpen(false) setIsSuggestedTopicsOpen(false) - setIsMeetingsOpen(false); setIsLiveNotesOpen(false); setIsBgTasksOpen(false); setIsEmailOpen(false); setIsWorkspaceOpen(false) + setIsMeetingsOpen(false); setIsLiveNotesOpen(false); setIsBgTasksOpen(false); setIsEmailOpen(false); setIsWorkspaceOpen(false); setIsKnowledgeViewOpen(false) setExpandedFrom(null) setIsGraphOpen(true) ensureGraphFileTab() @@ -3455,7 +3509,7 @@ function App() { setIsGraphOpen(false) setIsBrowserOpen(false) setIsSuggestedTopicsOpen(false) - setIsMeetingsOpen(false); setIsLiveNotesOpen(false); setIsBgTasksOpen(false); setIsEmailOpen(false); setIsWorkspaceOpen(false) + setIsMeetingsOpen(false); setIsLiveNotesOpen(false); setIsBgTasksOpen(false); setIsEmailOpen(false); setIsWorkspaceOpen(false); setIsKnowledgeViewOpen(false) setExpandedFrom(null) setIsRightPaneMaximized(false) setSelectedBackgroundTask(view.name) @@ -3468,7 +3522,7 @@ function App() { setIsRightPaneMaximized(false) setSelectedBackgroundTask(null) setIsSuggestedTopicsOpen(true) - setIsMeetingsOpen(false); setIsLiveNotesOpen(false); setIsBgTasksOpen(false); setIsEmailOpen(false); setIsWorkspaceOpen(false) + setIsMeetingsOpen(false); setIsLiveNotesOpen(false); setIsBgTasksOpen(false); setIsEmailOpen(false); setIsWorkspaceOpen(false); setIsKnowledgeViewOpen(false) ensureSuggestedTopicsFileTab() return case 'meetings': @@ -3484,6 +3538,7 @@ function App() { setIsBgTasksOpen(false) setIsEmailOpen(false) setIsWorkspaceOpen(false) + setIsKnowledgeViewOpen(false) ensureMeetingsFileTab() return case 'live-notes': @@ -3498,6 +3553,7 @@ function App() { setIsBgTasksOpen(false) setIsEmailOpen(false) setIsWorkspaceOpen(false) + setIsKnowledgeViewOpen(false) setIsLiveNotesOpen(true) ensureLiveNotesFileTab() return @@ -3514,6 +3570,7 @@ function App() { setIsBgTasksOpen(false) setIsEmailOpen(true) setIsWorkspaceOpen(false) + setIsKnowledgeViewOpen(false) ensureEmailFileTab() return case 'workspace': @@ -3529,9 +3586,26 @@ function App() { setIsBgTasksOpen(false) setIsEmailOpen(false) setIsWorkspaceOpen(true) + setIsKnowledgeViewOpen(false) setWorkspaceInitialPath(view.path ?? null) ensureWorkspaceFileTab() return + case 'knowledge-view': + setSelectedPath(null) + setIsGraphOpen(false) + setIsBrowserOpen(false) + setExpandedFrom(null) + setIsRightPaneMaximized(false) + setSelectedBackgroundTask(null) + setIsSuggestedTopicsOpen(false) + setIsMeetingsOpen(false) + setIsLiveNotesOpen(false) + setIsBgTasksOpen(false) + setIsEmailOpen(false) + setIsWorkspaceOpen(false) + setIsKnowledgeViewOpen(true) + ensureKnowledgeViewFileTab() + return case 'chat': setSelectedPath(null) setIsGraphOpen(false) @@ -3540,7 +3614,7 @@ function App() { setIsRightPaneMaximized(false) setSelectedBackgroundTask(null) setIsSuggestedTopicsOpen(false) - setIsMeetingsOpen(false); setIsLiveNotesOpen(false); setIsBgTasksOpen(false); setIsEmailOpen(false); setIsWorkspaceOpen(false) + setIsMeetingsOpen(false); setIsLiveNotesOpen(false); setIsBgTasksOpen(false); setIsEmailOpen(false); setIsWorkspaceOpen(false); setIsKnowledgeViewOpen(false) if (view.runId) { await loadRun(view.runId) } else { @@ -3548,7 +3622,7 @@ function App() { } return } - }, [ensureEmailFileTab, ensureMeetingsFileTab, ensureLiveNotesFileTab, ensureFileTabForPath, ensureGraphFileTab, ensureSuggestedTopicsFileTab, ensureWorkspaceFileTab, handleNewChat, isRightPaneMaximized, loadRun]) + }, [ensureEmailFileTab, ensureMeetingsFileTab, ensureLiveNotesFileTab, ensureFileTabForPath, ensureGraphFileTab, ensureSuggestedTopicsFileTab, ensureWorkspaceFileTab, ensureKnowledgeViewFileTab, handleNewChat, isRightPaneMaximized, loadRun]) const navigateToView = useCallback(async (nextView: ViewState) => { const current = currentViewState @@ -3870,7 +3944,7 @@ function App() { }, []) // Keyboard shortcut: Ctrl+L to toggle main chat view - const isFullScreenChat = !selectedPath && !isGraphOpen && !isSuggestedTopicsOpen && !isMeetingsOpen && !isLiveNotesOpen && !isBgTasksOpen && !isEmailOpen && !isWorkspaceOpen && !selectedBackgroundTask && !isBrowserOpen + const isFullScreenChat = !selectedPath && !isGraphOpen && !isSuggestedTopicsOpen && !isMeetingsOpen && !isLiveNotesOpen && !isBgTasksOpen && !isEmailOpen && !isWorkspaceOpen && !isKnowledgeViewOpen && !selectedBackgroundTask && !isBrowserOpen useEffect(() => { const handleKeyDown = (e: KeyboardEvent) => { if ((e.ctrlKey || e.metaKey) && e.key === 'l') { @@ -3955,11 +4029,11 @@ function App() { const handleTabKeyDown = (e: KeyboardEvent) => { const mod = e.metaKey || e.ctrlKey if (!mod) return - const rightPaneAvailable = Boolean((selectedPath || isGraphOpen || isSuggestedTopicsOpen || isMeetingsOpen || isLiveNotesOpen || isBgTasksOpen || isEmailOpen || isWorkspaceOpen) && isChatSidebarOpen) + const rightPaneAvailable = Boolean((selectedPath || isGraphOpen || isSuggestedTopicsOpen || isMeetingsOpen || isLiveNotesOpen || isBgTasksOpen || isEmailOpen || isWorkspaceOpen || isKnowledgeViewOpen) && isChatSidebarOpen) const targetPane: ShortcutPane = rightPaneAvailable ? (isRightPaneMaximized ? 'right' : activeShortcutPane) : 'left' - const inFileView = targetPane === 'left' && Boolean(selectedPath || isGraphOpen || isSuggestedTopicsOpen || isMeetingsOpen || isLiveNotesOpen || isBgTasksOpen || isEmailOpen || isWorkspaceOpen) + const inFileView = targetPane === 'left' && Boolean(selectedPath || isGraphOpen || isSuggestedTopicsOpen || isMeetingsOpen || isLiveNotesOpen || isBgTasksOpen || isEmailOpen || isWorkspaceOpen || isKnowledgeViewOpen) const selectedKnowledgePath = isGraphOpen ? GRAPH_TAB_PATH : isSuggestedTopicsOpen @@ -3974,6 +4048,8 @@ function App() { ? EMAIL_TAB_PATH : isWorkspaceOpen ? WORKSPACE_TAB_PATH + : isKnowledgeViewOpen + ? KNOWLEDGE_VIEW_TAB_PATH : selectedPath const targetFileTabId = activeFileTabId ?? ( selectedKnowledgePath @@ -4028,7 +4104,7 @@ function App() { } document.addEventListener('keydown', handleTabKeyDown) return () => document.removeEventListener('keydown', handleTabKeyDown) - }, [selectedPath, isGraphOpen, isSuggestedTopicsOpen, isMeetingsOpen, isLiveNotesOpen, isBgTasksOpen, isEmailOpen, isWorkspaceOpen, isChatSidebarOpen, isRightPaneMaximized, activeShortcutPane, chatTabs, fileTabs, activeChatTabId, activeFileTabId, closeChatTab, closeFileTab, switchChatTab, switchFileTab]) + }, [selectedPath, isGraphOpen, isSuggestedTopicsOpen, isMeetingsOpen, isLiveNotesOpen, isBgTasksOpen, isEmailOpen, isWorkspaceOpen, isKnowledgeViewOpen, isChatSidebarOpen, isRightPaneMaximized, activeShortcutPane, chatTabs, fileTabs, activeChatTabId, activeFileTabId, closeChatTab, closeFileTab, switchChatTab, switchFileTab]) const toggleExpand = (path: string, kind: 'file' | 'dir') => { if (kind === 'file') { @@ -4053,7 +4129,7 @@ function App() { }), }, })) - if (!selectedPath && !isGraphOpen && !isSuggestedTopicsOpen && !isMeetingsOpen && !isLiveNotesOpen && !isBgTasksOpen && !isEmailOpen && !isWorkspaceOpen && !selectedBackgroundTask) { + if (!selectedPath && !isGraphOpen && !isSuggestedTopicsOpen && !isMeetingsOpen && !isLiveNotesOpen && !isBgTasksOpen && !isEmailOpen && !isWorkspaceOpen && !isKnowledgeViewOpen && !selectedBackgroundTask) { setIsChatSidebarOpen(false) setIsRightPaneMaximized(false) } @@ -4179,26 +4255,33 @@ function App() { }, openGraph: () => { // From chat-only landing state, open graph directly in full knowledge view. - if (!selectedPath && !isGraphOpen && !isSuggestedTopicsOpen && !isMeetingsOpen && !isLiveNotesOpen && !isBgTasksOpen && !isEmailOpen && !isWorkspaceOpen && !selectedBackgroundTask) { + if (!selectedPath && !isGraphOpen && !isSuggestedTopicsOpen && !isMeetingsOpen && !isLiveNotesOpen && !isBgTasksOpen && !isEmailOpen && !isWorkspaceOpen && !isKnowledgeViewOpen && !selectedBackgroundTask) { setIsChatSidebarOpen(false) setIsRightPaneMaximized(false) } void navigateToView({ type: 'graph' }) }, openBases: () => { - if (!selectedPath && !isGraphOpen && !isSuggestedTopicsOpen && !isMeetingsOpen && !isLiveNotesOpen && !isBgTasksOpen && !isEmailOpen && !isWorkspaceOpen && !selectedBackgroundTask) { + if (!selectedPath && !isGraphOpen && !isSuggestedTopicsOpen && !isMeetingsOpen && !isLiveNotesOpen && !isBgTasksOpen && !isEmailOpen && !isWorkspaceOpen && !isKnowledgeViewOpen && !selectedBackgroundTask) { setIsChatSidebarOpen(false) setIsRightPaneMaximized(false) } void navigateToView({ type: 'file', path: BASES_DEFAULT_TAB_PATH }) }, openWorkspaceAt: (path?: string) => { - if (!selectedPath && !isGraphOpen && !isSuggestedTopicsOpen && !isMeetingsOpen && !isLiveNotesOpen && !isBgTasksOpen && !isEmailOpen && !isWorkspaceOpen && !selectedBackgroundTask) { + if (!selectedPath && !isGraphOpen && !isSuggestedTopicsOpen && !isMeetingsOpen && !isLiveNotesOpen && !isBgTasksOpen && !isEmailOpen && !isWorkspaceOpen && !isKnowledgeViewOpen && !selectedBackgroundTask) { setIsChatSidebarOpen(false) setIsRightPaneMaximized(false) } void navigateToView({ type: 'workspace', path }) }, + openKnowledgeView: () => { + if (!selectedPath && !isGraphOpen && !isSuggestedTopicsOpen && !isMeetingsOpen && !isLiveNotesOpen && !isBgTasksOpen && !isEmailOpen && !isWorkspaceOpen && !isKnowledgeViewOpen && !selectedBackgroundTask) { + setIsChatSidebarOpen(false) + setIsRightPaneMaximized(false) + } + void navigateToView({ type: 'knowledge-view' }) + }, createWorkspace: async (name: string): Promise => { const trimmed = name.trim() if (!trimmed) throw new Error('Name is required') @@ -4805,7 +4888,7 @@ function App() { const selectedTask = selectedBackgroundTask ? backgroundTasks.find(t => t.name === selectedBackgroundTask) : null - const isRightPaneContext = Boolean(selectedPath || isGraphOpen || isSuggestedTopicsOpen || isMeetingsOpen || isLiveNotesOpen || isBgTasksOpen || isEmailOpen || isWorkspaceOpen || isBrowserOpen) + const isRightPaneContext = Boolean(selectedPath || isGraphOpen || isSuggestedTopicsOpen || isMeetingsOpen || isLiveNotesOpen || isBgTasksOpen || isEmailOpen || isWorkspaceOpen || isKnowledgeViewOpen || isBrowserOpen) const isRightPaneOnlyMode = isRightPaneContext && isChatSidebarOpen && isRightPaneMaximized const shouldCollapseLeftPane = isRightPaneOnlyMode const openMarkdownTabs = React.useMemo(() => { @@ -4822,7 +4905,7 @@ function App() { return ( { - if (section === 'knowledge' && !selectedPath && !isGraphOpen && !isSuggestedTopicsOpen && !isMeetingsOpen && !isLiveNotesOpen && !isBgTasksOpen && !isEmailOpen && !isWorkspaceOpen) { + if (section === 'knowledge' && !selectedPath && !isGraphOpen && !isSuggestedTopicsOpen && !isMeetingsOpen && !isLiveNotesOpen && !isBgTasksOpen && !isEmailOpen && !isWorkspaceOpen && !isKnowledgeViewOpen) { void navigateToView({ type: 'file', path: BASES_DEFAULT_TAB_PATH }) } }}> @@ -4836,18 +4919,8 @@ function App() { { - setExpandedPaths((prev) => { - const next = new Set(prev) - if (next.has(path)) next.delete(path) - else next.add(path) - return next - }) - }} knowledgeActions={knowledgeActions} - onVoiceNoteCreated={handleVoiceNoteCreated} runs={runs} currentRunId={runId} processingRunIds={processingRunIds} @@ -4855,7 +4928,7 @@ function App() { onNewChat: handleNewChatTab, onSelectRun: (runIdToLoad) => { cancelRecordingIfActive() - if (selectedPath || isGraphOpen || isSuggestedTopicsOpen || isMeetingsOpen || isLiveNotesOpen || isBgTasksOpen || isEmailOpen || isWorkspaceOpen || isBrowserOpen) { + if (selectedPath || isGraphOpen || isSuggestedTopicsOpen || isMeetingsOpen || isLiveNotesOpen || isBgTasksOpen || isEmailOpen || isWorkspaceOpen || isKnowledgeViewOpen || isBrowserOpen) { setIsChatSidebarOpen(true) } @@ -4866,7 +4939,7 @@ function App() { return } // In two-pane mode (file/graph/browser), keep the middle pane and just swap chat context in the right sidebar. - if (selectedPath || isGraphOpen || isSuggestedTopicsOpen || isMeetingsOpen || isLiveNotesOpen || isBgTasksOpen || isEmailOpen || isWorkspaceOpen || isBrowserOpen) { + if (selectedPath || isGraphOpen || isSuggestedTopicsOpen || isMeetingsOpen || isLiveNotesOpen || isBgTasksOpen || isEmailOpen || isWorkspaceOpen || isKnowledgeViewOpen || isBrowserOpen) { setChatTabs(prev => prev.map(t => t.id === activeChatTabId ? { ...t, runId: runIdToLoad } : t)) loadRun(runIdToLoad) return @@ -4890,14 +4963,14 @@ function App() { } else { // Only one tab, reset it to new chat setChatTabs([{ id: tabForRun.id, runId: null }]) - if (selectedPath || isGraphOpen || isSuggestedTopicsOpen || isMeetingsOpen || isLiveNotesOpen || isBgTasksOpen || isEmailOpen || isWorkspaceOpen || isBrowserOpen) { + if (selectedPath || isGraphOpen || isSuggestedTopicsOpen || isMeetingsOpen || isLiveNotesOpen || isBgTasksOpen || isEmailOpen || isWorkspaceOpen || isKnowledgeViewOpen || isBrowserOpen) { handleNewChat() } else { void navigateToView({ type: 'chat', runId: null }) } } } else if (runId === runIdToDelete) { - if (selectedPath || isGraphOpen || isSuggestedTopicsOpen || isMeetingsOpen || isLiveNotesOpen || isBgTasksOpen || isEmailOpen || isWorkspaceOpen || isBrowserOpen) { + if (selectedPath || isGraphOpen || isSuggestedTopicsOpen || isMeetingsOpen || isLiveNotesOpen || isBgTasksOpen || isEmailOpen || isWorkspaceOpen || isKnowledgeViewOpen || isBrowserOpen) { setChatTabs(prev => prev.map(t => t.id === activeChatTabId ? { ...t, runId: null } : t)) handleNewChat() } else { @@ -4945,7 +5018,7 @@ function App() { canNavigateForward={canNavigateForward} collapsedLeftPaddingPx={collapsedLeftPaddingPx} > - {(selectedPath || isGraphOpen || isSuggestedTopicsOpen || isMeetingsOpen || isLiveNotesOpen || isBgTasksOpen || isEmailOpen || isWorkspaceOpen) && fileTabs.length >= 1 ? ( + {(selectedPath || isGraphOpen || isSuggestedTopicsOpen || isMeetingsOpen || isLiveNotesOpen || isBgTasksOpen || isEmailOpen || isWorkspaceOpen || isKnowledgeViewOpen) && fileTabs.length >= 1 ? ( t.id} onSwitchTab={switchFileTab} onCloseTab={closeFileTab} - allowSingleTabClose={fileTabs.length === 1 && (isGraphOpen || isSuggestedTopicsOpen || isMeetingsOpen || isLiveNotesOpen || isBgTasksOpen || isEmailOpen || isWorkspaceOpen || (selectedPath != null && isBaseFilePath(selectedPath)))} + allowSingleTabClose={fileTabs.length === 1 && (isGraphOpen || isSuggestedTopicsOpen || isMeetingsOpen || isLiveNotesOpen || isBgTasksOpen || isEmailOpen || isWorkspaceOpen || isKnowledgeViewOpen || (selectedPath != null && isBaseFilePath(selectedPath)))} /> ) : ( Version history )} - {!selectedPath && !isGraphOpen && !isSuggestedTopicsOpen && !isMeetingsOpen && !isLiveNotesOpen && !isBgTasksOpen && !isEmailOpen && !isWorkspaceOpen && !selectedTask && !isBrowserOpen && ( + {!selectedPath && !isGraphOpen && !isSuggestedTopicsOpen && !isMeetingsOpen && !isLiveNotesOpen && !isBgTasksOpen && !isEmailOpen && !isWorkspaceOpen && !isKnowledgeViewOpen && !selectedTask && !isBrowserOpen && (