mirror of
https://github.com/rowboatlabs/rowboat.git
synced 2026-07-12 21:02:17 +02:00
added chat history page
This commit is contained in:
parent
e678dd71a0
commit
f8100d353d
3 changed files with 388 additions and 103 deletions
|
|
@ -27,6 +27,7 @@ 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 { ChatHistoryView } from '@/components/chat-history-view';
|
||||
import { MeetingsView } from '@/components/meetings-view';
|
||||
import { SidebarSectionProvider } from '@/contexts/sidebar-context';
|
||||
import {
|
||||
|
|
@ -187,6 +188,7 @@ 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 CHAT_HISTORY_TAB_PATH = '__rowboat_chat_history__'
|
||||
const BASES_DEFAULT_TAB_PATH = '__rowboat_bases_default__'
|
||||
|
||||
const clampNumber = (value: number, min: number, max: number) =>
|
||||
|
|
@ -322,6 +324,7 @@ 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 isChatHistoryTabPath = (path: string) => path === CHAT_HISTORY_TAB_PATH
|
||||
const isBaseFilePath = (path: string) => path.endsWith('.base') || path === BASES_DEFAULT_TAB_PATH
|
||||
|
||||
const getSuggestedTopicTargetFolder = (category?: string) => {
|
||||
|
|
@ -574,6 +577,7 @@ type ViewState =
|
|||
| { type: 'email' }
|
||||
| { type: 'workspace'; path?: string }
|
||||
| { type: 'knowledge-view' }
|
||||
| { type: 'chat-history' }
|
||||
|
||||
function viewStatesEqual(a: ViewState, b: ViewState): boolean {
|
||||
if (a.type !== b.type) return false
|
||||
|
|
@ -630,6 +634,8 @@ function parseDeepLink(input: string): ViewState | null {
|
|||
}
|
||||
case 'knowledge-view':
|
||||
return { type: 'knowledge-view' }
|
||||
case 'chat-history':
|
||||
return { type: 'chat-history' }
|
||||
default:
|
||||
return null
|
||||
}
|
||||
|
|
@ -768,6 +774,7 @@ function App() {
|
|||
const [isWorkspaceOpen, setIsWorkspaceOpen] = useState(false)
|
||||
const [workspaceInitialPath, setWorkspaceInitialPath] = useState<string | null>(null)
|
||||
const [isKnowledgeViewOpen, setIsKnowledgeViewOpen] = useState(false)
|
||||
const [isChatHistoryOpen, setIsChatHistoryOpen] = useState(false)
|
||||
const [expandedFrom, setExpandedFrom] = useState<{
|
||||
path: string | null
|
||||
graph: boolean
|
||||
|
|
@ -1106,6 +1113,7 @@ function App() {
|
|||
if (isEmailTabPath(tab.path)) return 'Email'
|
||||
if (isWorkspaceTabPath(tab.path)) return 'Workspace'
|
||||
if (isKnowledgeViewTabPath(tab.path)) return 'Knowledge'
|
||||
if (isChatHistoryTabPath(tab.path)) return 'Chat history'
|
||||
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
|
||||
|
|
@ -2818,7 +2826,7 @@ function App() {
|
|||
setActiveFileTabId(existingTab.id)
|
||||
setIsGraphOpen(false)
|
||||
setIsSuggestedTopicsOpen(false)
|
||||
setIsMeetingsOpen(false); setIsLiveNotesOpen(false); setIsBgTasksOpen(false); setIsEmailOpen(false); setIsWorkspaceOpen(false); setIsKnowledgeViewOpen(false)
|
||||
setIsMeetingsOpen(false); setIsLiveNotesOpen(false); setIsBgTasksOpen(false); setIsEmailOpen(false); setIsWorkspaceOpen(false); setIsKnowledgeViewOpen(false); setIsChatHistoryOpen(false)
|
||||
setSelectedPath(path)
|
||||
return
|
||||
}
|
||||
|
|
@ -2827,7 +2835,7 @@ function App() {
|
|||
setActiveFileTabId(id)
|
||||
setIsGraphOpen(false)
|
||||
setIsSuggestedTopicsOpen(false)
|
||||
setIsMeetingsOpen(false); setIsLiveNotesOpen(false); setIsBgTasksOpen(false); setIsEmailOpen(false); setIsWorkspaceOpen(false); setIsKnowledgeViewOpen(false)
|
||||
setIsMeetingsOpen(false); setIsLiveNotesOpen(false); setIsBgTasksOpen(false); setIsEmailOpen(false); setIsWorkspaceOpen(false); setIsKnowledgeViewOpen(false); setIsChatHistoryOpen(false)
|
||||
setSelectedPath(path)
|
||||
}, [fileTabs, dismissBrowserOverlay])
|
||||
|
||||
|
|
@ -2846,14 +2854,14 @@ function App() {
|
|||
setSelectedPath(null)
|
||||
setIsGraphOpen(true)
|
||||
setIsSuggestedTopicsOpen(false)
|
||||
setIsMeetingsOpen(false); setIsLiveNotesOpen(false); setIsBgTasksOpen(false); setIsEmailOpen(false); setIsWorkspaceOpen(false); setIsKnowledgeViewOpen(false)
|
||||
setIsMeetingsOpen(false); setIsLiveNotesOpen(false); setIsBgTasksOpen(false); setIsEmailOpen(false); setIsWorkspaceOpen(false); setIsKnowledgeViewOpen(false); setIsChatHistoryOpen(false)
|
||||
return
|
||||
}
|
||||
if (isSuggestedTopicsTabPath(tab.path)) {
|
||||
setSelectedPath(null)
|
||||
setIsGraphOpen(false)
|
||||
setIsSuggestedTopicsOpen(true)
|
||||
setIsMeetingsOpen(false); setIsLiveNotesOpen(false); setIsBgTasksOpen(false); setIsEmailOpen(false); setIsWorkspaceOpen(false); setIsKnowledgeViewOpen(false)
|
||||
setIsMeetingsOpen(false); setIsLiveNotesOpen(false); setIsBgTasksOpen(false); setIsEmailOpen(false); setIsWorkspaceOpen(false); setIsKnowledgeViewOpen(false); setIsChatHistoryOpen(false)
|
||||
return
|
||||
}
|
||||
if (isLiveNotesTabPath(tab.path)) {
|
||||
|
|
@ -2865,6 +2873,7 @@ function App() {
|
|||
setIsEmailOpen(false)
|
||||
setIsWorkspaceOpen(false)
|
||||
setIsKnowledgeViewOpen(false)
|
||||
setIsChatHistoryOpen(false)
|
||||
setIsLiveNotesOpen(true)
|
||||
return
|
||||
}
|
||||
|
|
@ -2877,6 +2886,7 @@ function App() {
|
|||
setIsEmailOpen(false)
|
||||
setIsWorkspaceOpen(false)
|
||||
setIsKnowledgeViewOpen(false)
|
||||
setIsChatHistoryOpen(false)
|
||||
setIsBgTasksOpen(true)
|
||||
return
|
||||
}
|
||||
|
|
@ -2890,6 +2900,7 @@ function App() {
|
|||
setIsEmailOpen(false)
|
||||
setIsWorkspaceOpen(false)
|
||||
setIsKnowledgeViewOpen(false)
|
||||
setIsChatHistoryOpen(false)
|
||||
return
|
||||
}
|
||||
if (isEmailTabPath(tab.path)) {
|
||||
|
|
@ -2901,6 +2912,7 @@ function App() {
|
|||
setIsBgTasksOpen(false)
|
||||
setIsWorkspaceOpen(false)
|
||||
setIsKnowledgeViewOpen(false)
|
||||
setIsChatHistoryOpen(false)
|
||||
setIsEmailOpen(true)
|
||||
return
|
||||
}
|
||||
|
|
@ -2913,6 +2925,7 @@ function App() {
|
|||
setIsBgTasksOpen(false)
|
||||
setIsEmailOpen(false)
|
||||
setIsKnowledgeViewOpen(false)
|
||||
setIsChatHistoryOpen(false)
|
||||
setIsWorkspaceOpen(true)
|
||||
return
|
||||
}
|
||||
|
|
@ -2925,18 +2938,32 @@ function App() {
|
|||
setIsBgTasksOpen(false)
|
||||
setIsEmailOpen(false)
|
||||
setIsWorkspaceOpen(false)
|
||||
setIsChatHistoryOpen(false)
|
||||
setIsKnowledgeViewOpen(true)
|
||||
return
|
||||
}
|
||||
if (isChatHistoryTabPath(tab.path)) {
|
||||
setSelectedPath(null)
|
||||
setIsGraphOpen(false)
|
||||
setIsSuggestedTopicsOpen(false)
|
||||
setIsMeetingsOpen(false)
|
||||
setIsLiveNotesOpen(false)
|
||||
setIsBgTasksOpen(false)
|
||||
setIsEmailOpen(false)
|
||||
setIsWorkspaceOpen(false)
|
||||
setIsKnowledgeViewOpen(false)
|
||||
setIsChatHistoryOpen(true)
|
||||
return
|
||||
}
|
||||
setIsGraphOpen(false)
|
||||
setIsSuggestedTopicsOpen(false)
|
||||
setIsMeetingsOpen(false); setIsLiveNotesOpen(false); setIsBgTasksOpen(false); setIsEmailOpen(false); setIsWorkspaceOpen(false); setIsKnowledgeViewOpen(false)
|
||||
setIsMeetingsOpen(false); setIsLiveNotesOpen(false); setIsBgTasksOpen(false); setIsEmailOpen(false); setIsWorkspaceOpen(false); setIsKnowledgeViewOpen(false); setIsChatHistoryOpen(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) && !isKnowledgeViewTabPath(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) && !isChatHistoryTabPath(closingTab.path) && !isBaseFilePath(closingTab.path)) {
|
||||
removeEditorCacheForPath(closingTab.path)
|
||||
initialContentByPathRef.current.delete(closingTab.path)
|
||||
untitledRenameReadyPathsRef.current.delete(closingTab.path)
|
||||
|
|
@ -2959,7 +2986,7 @@ function App() {
|
|||
setSelectedPath(null)
|
||||
setIsGraphOpen(false)
|
||||
setIsSuggestedTopicsOpen(false)
|
||||
setIsMeetingsOpen(false); setIsLiveNotesOpen(false); setIsBgTasksOpen(false); setIsEmailOpen(false); setIsWorkspaceOpen(false); setIsKnowledgeViewOpen(false)
|
||||
setIsMeetingsOpen(false); setIsLiveNotesOpen(false); setIsBgTasksOpen(false); setIsEmailOpen(false); setIsWorkspaceOpen(false); setIsKnowledgeViewOpen(false); setIsChatHistoryOpen(false)
|
||||
return []
|
||||
}
|
||||
const idx = prev.findIndex(t => t.id === tabId)
|
||||
|
|
@ -2973,12 +3000,12 @@ function App() {
|
|||
setSelectedPath(null)
|
||||
setIsGraphOpen(true)
|
||||
setIsSuggestedTopicsOpen(false)
|
||||
setIsMeetingsOpen(false); setIsLiveNotesOpen(false); setIsBgTasksOpen(false); setIsEmailOpen(false); setIsWorkspaceOpen(false); setIsKnowledgeViewOpen(false)
|
||||
setIsMeetingsOpen(false); setIsLiveNotesOpen(false); setIsBgTasksOpen(false); setIsEmailOpen(false); setIsWorkspaceOpen(false); setIsKnowledgeViewOpen(false); setIsChatHistoryOpen(false)
|
||||
} else if (isSuggestedTopicsTabPath(newActiveTab.path)) {
|
||||
setSelectedPath(null)
|
||||
setIsGraphOpen(false)
|
||||
setIsSuggestedTopicsOpen(true)
|
||||
setIsMeetingsOpen(false); setIsLiveNotesOpen(false); setIsBgTasksOpen(false); setIsEmailOpen(false); setIsWorkspaceOpen(false); setIsKnowledgeViewOpen(false)
|
||||
setIsMeetingsOpen(false); setIsLiveNotesOpen(false); setIsBgTasksOpen(false); setIsEmailOpen(false); setIsWorkspaceOpen(false); setIsKnowledgeViewOpen(false); setIsChatHistoryOpen(false)
|
||||
} else if (isMeetingsTabPath(newActiveTab.path)) {
|
||||
setSelectedPath(null)
|
||||
setIsGraphOpen(false)
|
||||
|
|
@ -2989,6 +3016,7 @@ function App() {
|
|||
setIsEmailOpen(false)
|
||||
setIsWorkspaceOpen(false)
|
||||
setIsKnowledgeViewOpen(false)
|
||||
setIsChatHistoryOpen(false)
|
||||
} else if (isLiveNotesTabPath(newActiveTab.path)) {
|
||||
setSelectedPath(null)
|
||||
setIsGraphOpen(false)
|
||||
|
|
@ -2998,6 +3026,7 @@ function App() {
|
|||
setIsEmailOpen(false)
|
||||
setIsWorkspaceOpen(false)
|
||||
setIsKnowledgeViewOpen(false)
|
||||
setIsChatHistoryOpen(false)
|
||||
setIsLiveNotesOpen(true)
|
||||
} else if (isBgTasksTabPath(newActiveTab.path)) {
|
||||
setSelectedPath(null)
|
||||
|
|
@ -3009,6 +3038,7 @@ function App() {
|
|||
setIsEmailOpen(false)
|
||||
setIsWorkspaceOpen(false)
|
||||
setIsKnowledgeViewOpen(false)
|
||||
setIsChatHistoryOpen(false)
|
||||
} else if (isEmailTabPath(newActiveTab.path)) {
|
||||
setSelectedPath(null)
|
||||
setIsGraphOpen(false)
|
||||
|
|
@ -3018,6 +3048,7 @@ function App() {
|
|||
setIsBgTasksOpen(false)
|
||||
setIsWorkspaceOpen(false)
|
||||
setIsKnowledgeViewOpen(false)
|
||||
setIsChatHistoryOpen(false)
|
||||
setIsEmailOpen(true)
|
||||
} else if (isWorkspaceTabPath(newActiveTab.path)) {
|
||||
setSelectedPath(null)
|
||||
|
|
@ -3028,6 +3059,7 @@ function App() {
|
|||
setIsBgTasksOpen(false)
|
||||
setIsEmailOpen(false)
|
||||
setIsKnowledgeViewOpen(false)
|
||||
setIsChatHistoryOpen(false)
|
||||
setIsWorkspaceOpen(true)
|
||||
} else if (isKnowledgeViewTabPath(newActiveTab.path)) {
|
||||
setSelectedPath(null)
|
||||
|
|
@ -3038,11 +3070,23 @@ function App() {
|
|||
setIsBgTasksOpen(false)
|
||||
setIsEmailOpen(false)
|
||||
setIsWorkspaceOpen(false)
|
||||
setIsChatHistoryOpen(false)
|
||||
setIsKnowledgeViewOpen(true)
|
||||
} else if (isChatHistoryTabPath(newActiveTab.path)) {
|
||||
setSelectedPath(null)
|
||||
setIsGraphOpen(false)
|
||||
setIsSuggestedTopicsOpen(false)
|
||||
setIsMeetingsOpen(false)
|
||||
setIsLiveNotesOpen(false)
|
||||
setIsBgTasksOpen(false)
|
||||
setIsEmailOpen(false)
|
||||
setIsWorkspaceOpen(false)
|
||||
setIsKnowledgeViewOpen(false)
|
||||
setIsChatHistoryOpen(true)
|
||||
} else {
|
||||
setIsGraphOpen(false)
|
||||
setIsSuggestedTopicsOpen(false)
|
||||
setIsMeetingsOpen(false); setIsLiveNotesOpen(false); setIsBgTasksOpen(false); setIsEmailOpen(false); setIsWorkspaceOpen(false); setIsKnowledgeViewOpen(false)
|
||||
setIsMeetingsOpen(false); setIsLiveNotesOpen(false); setIsBgTasksOpen(false); setIsEmailOpen(false); setIsWorkspaceOpen(false); setIsKnowledgeViewOpen(false); setIsChatHistoryOpen(false)
|
||||
setSelectedPath(newActiveTab.path)
|
||||
}
|
||||
}
|
||||
|
|
@ -3073,7 +3117,7 @@ function App() {
|
|||
dismissBrowserOverlay()
|
||||
handleNewChat()
|
||||
// Left-pane "new chat" should always open full chat view.
|
||||
if (selectedPath || isGraphOpen || isSuggestedTopicsOpen || isMeetingsOpen || isLiveNotesOpen || isBgTasksOpen || isEmailOpen || isWorkspaceOpen || isKnowledgeViewOpen) {
|
||||
if (selectedPath || isGraphOpen || isSuggestedTopicsOpen || isMeetingsOpen || isLiveNotesOpen || isBgTasksOpen || isEmailOpen || isWorkspaceOpen || isKnowledgeViewOpen || isChatHistoryOpen) {
|
||||
setExpandedFrom({
|
||||
path: selectedPath,
|
||||
graph: isGraphOpen,
|
||||
|
|
@ -3090,8 +3134,8 @@ function App() {
|
|||
setSelectedPath(null)
|
||||
setIsGraphOpen(false)
|
||||
setIsSuggestedTopicsOpen(false)
|
||||
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])
|
||||
setIsMeetingsOpen(false); setIsLiveNotesOpen(false); setIsBgTasksOpen(false); setIsEmailOpen(false); setIsWorkspaceOpen(false); setIsKnowledgeViewOpen(false); setIsChatHistoryOpen(false)
|
||||
}, [chatTabs, activeChatTabId, dismissBrowserOverlay, handleNewChat, selectedPath, isGraphOpen, isSuggestedTopicsOpen, isMeetingsOpen, isLiveNotesOpen, isBgTasksOpen, isEmailOpen, isWorkspaceOpen, isKnowledgeViewOpen, isChatHistoryOpen])
|
||||
|
||||
// Sidebar variant: create/switch chat tab without leaving file/graph context.
|
||||
const handleNewChatTabInSidebar = useCallback(() => {
|
||||
|
|
@ -3223,7 +3267,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 || isKnowledgeViewOpen) {
|
||||
if (selectedPath || isGraphOpen || isSuggestedTopicsOpen || isMeetingsOpen || isLiveNotesOpen || isBgTasksOpen || isEmailOpen || isWorkspaceOpen || isKnowledgeViewOpen || isChatHistoryOpen) {
|
||||
setExpandedFrom({
|
||||
path: selectedPath,
|
||||
graph: isGraphOpen,
|
||||
|
|
@ -3239,19 +3283,19 @@ function App() {
|
|||
setSelectedPath(null)
|
||||
setIsGraphOpen(false)
|
||||
setIsSuggestedTopicsOpen(false)
|
||||
setIsMeetingsOpen(false); setIsLiveNotesOpen(false); setIsBgTasksOpen(false); setIsEmailOpen(false); setIsWorkspaceOpen(false); setIsKnowledgeViewOpen(false)
|
||||
}, [selectedPath, isGraphOpen, isSuggestedTopicsOpen, isMeetingsOpen, isLiveNotesOpen, isBgTasksOpen, isEmailOpen, isWorkspaceOpen, isKnowledgeViewOpen, dismissBrowserOverlay])
|
||||
setIsMeetingsOpen(false); setIsLiveNotesOpen(false); setIsBgTasksOpen(false); setIsEmailOpen(false); setIsWorkspaceOpen(false); setIsKnowledgeViewOpen(false); setIsChatHistoryOpen(false)
|
||||
}, [selectedPath, isGraphOpen, isSuggestedTopicsOpen, isMeetingsOpen, isLiveNotesOpen, isBgTasksOpen, isEmailOpen, isWorkspaceOpen, isKnowledgeViewOpen, isChatHistoryOpen, dismissBrowserOverlay])
|
||||
|
||||
const handleCloseFullScreenChat = useCallback(() => {
|
||||
if (expandedFrom) {
|
||||
if (expandedFrom.graph) {
|
||||
setIsGraphOpen(true)
|
||||
setIsSuggestedTopicsOpen(false)
|
||||
setIsMeetingsOpen(false); setIsLiveNotesOpen(false); setIsBgTasksOpen(false); setIsEmailOpen(false); setIsWorkspaceOpen(false); setIsKnowledgeViewOpen(false)
|
||||
setIsMeetingsOpen(false); setIsLiveNotesOpen(false); setIsBgTasksOpen(false); setIsEmailOpen(false); setIsWorkspaceOpen(false); setIsKnowledgeViewOpen(false); setIsChatHistoryOpen(false)
|
||||
} else if (expandedFrom.suggestedTopics) {
|
||||
setIsGraphOpen(false)
|
||||
setIsSuggestedTopicsOpen(true)
|
||||
setIsMeetingsOpen(false); setIsLiveNotesOpen(false); setIsBgTasksOpen(false); setIsEmailOpen(false); setIsWorkspaceOpen(false); setIsKnowledgeViewOpen(false)
|
||||
setIsMeetingsOpen(false); setIsLiveNotesOpen(false); setIsBgTasksOpen(false); setIsEmailOpen(false); setIsWorkspaceOpen(false); setIsKnowledgeViewOpen(false); setIsChatHistoryOpen(false)
|
||||
} else if (expandedFrom.meetings) {
|
||||
setIsGraphOpen(false)
|
||||
setIsSuggestedTopicsOpen(false)
|
||||
|
|
@ -3283,7 +3327,7 @@ function App() {
|
|||
} else if (expandedFrom.path) {
|
||||
setIsGraphOpen(false)
|
||||
setIsSuggestedTopicsOpen(false)
|
||||
setIsMeetingsOpen(false); setIsLiveNotesOpen(false); setIsBgTasksOpen(false); setIsEmailOpen(false); setIsWorkspaceOpen(false); setIsKnowledgeViewOpen(false)
|
||||
setIsMeetingsOpen(false); setIsLiveNotesOpen(false); setIsBgTasksOpen(false); setIsEmailOpen(false); setIsWorkspaceOpen(false); setIsKnowledgeViewOpen(false); setIsChatHistoryOpen(false)
|
||||
setSelectedPath(expandedFrom.path)
|
||||
}
|
||||
setExpandedFrom(null)
|
||||
|
|
@ -3299,10 +3343,11 @@ function App() {
|
|||
if (isSuggestedTopicsOpen) return { type: 'suggested-topics' }
|
||||
if (isWorkspaceOpen) return { type: 'workspace', path: workspaceInitialPath ?? undefined }
|
||||
if (isKnowledgeViewOpen) return { type: 'knowledge-view' }
|
||||
if (isChatHistoryOpen) return { type: 'chat-history' }
|
||||
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, isKnowledgeViewOpen, workspaceInitialPath, runId])
|
||||
}, [selectedBackgroundTask, isEmailOpen, isMeetingsOpen, isLiveNotesOpen, isBgTasksOpen, isSuggestedTopicsOpen, selectedPath, isGraphOpen, isWorkspaceOpen, isKnowledgeViewOpen, isChatHistoryOpen, workspaceInitialPath, runId])
|
||||
|
||||
const appendUnique = useCallback((stack: ViewState[], entry: ViewState) => {
|
||||
const last = stack[stack.length - 1]
|
||||
|
|
@ -3425,6 +3470,17 @@ function App() {
|
|||
setActiveFileTabId(id)
|
||||
}, [fileTabs])
|
||||
|
||||
const ensureChatHistoryFileTab = useCallback(() => {
|
||||
const existing = fileTabs.find((tab) => isChatHistoryTabPath(tab.path))
|
||||
if (existing) {
|
||||
setActiveFileTabId(existing.id)
|
||||
return
|
||||
}
|
||||
const id = newFileTabId()
|
||||
setFileTabs((prev) => [...prev, { id, path: CHAT_HISTORY_TAB_PATH }])
|
||||
setActiveFileTabId(id)
|
||||
}, [fileTabs])
|
||||
|
||||
const openEmailView = useCallback(() => {
|
||||
setSelectedPath(null)
|
||||
setIsGraphOpen(false)
|
||||
|
|
@ -3447,7 +3503,7 @@ function App() {
|
|||
setIsGraphOpen(false)
|
||||
setIsBrowserOpen(false)
|
||||
setIsSuggestedTopicsOpen(false)
|
||||
setIsMeetingsOpen(false); setIsLiveNotesOpen(false); setIsBgTasksOpen(false); setIsEmailOpen(false); setIsWorkspaceOpen(false); setIsKnowledgeViewOpen(false)
|
||||
setIsMeetingsOpen(false); setIsLiveNotesOpen(false); setIsBgTasksOpen(false); setIsEmailOpen(false); setIsWorkspaceOpen(false); setIsKnowledgeViewOpen(false); setIsChatHistoryOpen(false)
|
||||
setSelectedBackgroundTask(null)
|
||||
setExpandedFrom(null)
|
||||
setIsRightPaneMaximized(false)
|
||||
|
|
@ -3481,7 +3537,7 @@ function App() {
|
|||
// visible in the middle pane.
|
||||
setIsBrowserOpen(false)
|
||||
setIsSuggestedTopicsOpen(false)
|
||||
setIsMeetingsOpen(false); setIsLiveNotesOpen(false); setIsBgTasksOpen(false); setIsEmailOpen(false); setIsWorkspaceOpen(false); setIsKnowledgeViewOpen(false)
|
||||
setIsMeetingsOpen(false); setIsLiveNotesOpen(false); setIsBgTasksOpen(false); setIsEmailOpen(false); setIsWorkspaceOpen(false); setIsKnowledgeViewOpen(false); setIsChatHistoryOpen(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.
|
||||
|
|
@ -3496,7 +3552,7 @@ function App() {
|
|||
setSelectedPath(null)
|
||||
setIsBrowserOpen(false)
|
||||
setIsSuggestedTopicsOpen(false)
|
||||
setIsMeetingsOpen(false); setIsLiveNotesOpen(false); setIsBgTasksOpen(false); setIsEmailOpen(false); setIsWorkspaceOpen(false); setIsKnowledgeViewOpen(false)
|
||||
setIsMeetingsOpen(false); setIsLiveNotesOpen(false); setIsBgTasksOpen(false); setIsEmailOpen(false); setIsWorkspaceOpen(false); setIsKnowledgeViewOpen(false); setIsChatHistoryOpen(false)
|
||||
setExpandedFrom(null)
|
||||
setIsGraphOpen(true)
|
||||
ensureGraphFileTab()
|
||||
|
|
@ -3509,7 +3565,7 @@ function App() {
|
|||
setIsGraphOpen(false)
|
||||
setIsBrowserOpen(false)
|
||||
setIsSuggestedTopicsOpen(false)
|
||||
setIsMeetingsOpen(false); setIsLiveNotesOpen(false); setIsBgTasksOpen(false); setIsEmailOpen(false); setIsWorkspaceOpen(false); setIsKnowledgeViewOpen(false)
|
||||
setIsMeetingsOpen(false); setIsLiveNotesOpen(false); setIsBgTasksOpen(false); setIsEmailOpen(false); setIsWorkspaceOpen(false); setIsKnowledgeViewOpen(false); setIsChatHistoryOpen(false)
|
||||
setExpandedFrom(null)
|
||||
setIsRightPaneMaximized(false)
|
||||
setSelectedBackgroundTask(view.name)
|
||||
|
|
@ -3522,7 +3578,7 @@ function App() {
|
|||
setIsRightPaneMaximized(false)
|
||||
setSelectedBackgroundTask(null)
|
||||
setIsSuggestedTopicsOpen(true)
|
||||
setIsMeetingsOpen(false); setIsLiveNotesOpen(false); setIsBgTasksOpen(false); setIsEmailOpen(false); setIsWorkspaceOpen(false); setIsKnowledgeViewOpen(false)
|
||||
setIsMeetingsOpen(false); setIsLiveNotesOpen(false); setIsBgTasksOpen(false); setIsEmailOpen(false); setIsWorkspaceOpen(false); setIsKnowledgeViewOpen(false); setIsChatHistoryOpen(false)
|
||||
ensureSuggestedTopicsFileTab()
|
||||
return
|
||||
case 'meetings':
|
||||
|
|
@ -3539,6 +3595,7 @@ function App() {
|
|||
setIsEmailOpen(false)
|
||||
setIsWorkspaceOpen(false)
|
||||
setIsKnowledgeViewOpen(false)
|
||||
setIsChatHistoryOpen(false)
|
||||
ensureMeetingsFileTab()
|
||||
return
|
||||
case 'live-notes':
|
||||
|
|
@ -3554,6 +3611,7 @@ function App() {
|
|||
setIsEmailOpen(false)
|
||||
setIsWorkspaceOpen(false)
|
||||
setIsKnowledgeViewOpen(false)
|
||||
setIsChatHistoryOpen(false)
|
||||
setIsLiveNotesOpen(true)
|
||||
ensureLiveNotesFileTab()
|
||||
return
|
||||
|
|
@ -3571,6 +3629,7 @@ function App() {
|
|||
setIsEmailOpen(true)
|
||||
setIsWorkspaceOpen(false)
|
||||
setIsKnowledgeViewOpen(false)
|
||||
setIsChatHistoryOpen(false)
|
||||
ensureEmailFileTab()
|
||||
return
|
||||
case 'workspace':
|
||||
|
|
@ -3587,6 +3646,7 @@ function App() {
|
|||
setIsEmailOpen(false)
|
||||
setIsWorkspaceOpen(true)
|
||||
setIsKnowledgeViewOpen(false)
|
||||
setIsChatHistoryOpen(false)
|
||||
setWorkspaceInitialPath(view.path ?? null)
|
||||
ensureWorkspaceFileTab()
|
||||
return
|
||||
|
|
@ -3604,8 +3664,26 @@ function App() {
|
|||
setIsEmailOpen(false)
|
||||
setIsWorkspaceOpen(false)
|
||||
setIsKnowledgeViewOpen(true)
|
||||
setIsChatHistoryOpen(false)
|
||||
ensureKnowledgeViewFileTab()
|
||||
return
|
||||
case 'chat-history':
|
||||
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(false)
|
||||
setIsChatHistoryOpen(true)
|
||||
ensureChatHistoryFileTab()
|
||||
return
|
||||
case 'chat':
|
||||
setSelectedPath(null)
|
||||
setIsGraphOpen(false)
|
||||
|
|
@ -3614,7 +3692,7 @@ function App() {
|
|||
setIsRightPaneMaximized(false)
|
||||
setSelectedBackgroundTask(null)
|
||||
setIsSuggestedTopicsOpen(false)
|
||||
setIsMeetingsOpen(false); setIsLiveNotesOpen(false); setIsBgTasksOpen(false); setIsEmailOpen(false); setIsWorkspaceOpen(false); setIsKnowledgeViewOpen(false)
|
||||
setIsMeetingsOpen(false); setIsLiveNotesOpen(false); setIsBgTasksOpen(false); setIsEmailOpen(false); setIsWorkspaceOpen(false); setIsKnowledgeViewOpen(false); setIsChatHistoryOpen(false)
|
||||
if (view.runId) {
|
||||
await loadRun(view.runId)
|
||||
} else {
|
||||
|
|
@ -3622,7 +3700,7 @@ function App() {
|
|||
}
|
||||
return
|
||||
}
|
||||
}, [ensureEmailFileTab, ensureMeetingsFileTab, ensureLiveNotesFileTab, ensureFileTabForPath, ensureGraphFileTab, ensureSuggestedTopicsFileTab, ensureWorkspaceFileTab, ensureKnowledgeViewFileTab, handleNewChat, isRightPaneMaximized, loadRun])
|
||||
}, [ensureEmailFileTab, ensureMeetingsFileTab, ensureLiveNotesFileTab, ensureFileTabForPath, ensureGraphFileTab, ensureSuggestedTopicsFileTab, ensureWorkspaceFileTab, ensureKnowledgeViewFileTab, ensureChatHistoryFileTab, handleNewChat, isRightPaneMaximized, loadRun])
|
||||
|
||||
const navigateToView = useCallback(async (nextView: ViewState) => {
|
||||
const current = currentViewState
|
||||
|
|
@ -3944,7 +4022,7 @@ function App() {
|
|||
}, [])
|
||||
|
||||
// Keyboard shortcut: Ctrl+L to toggle main chat view
|
||||
const isFullScreenChat = !selectedPath && !isGraphOpen && !isSuggestedTopicsOpen && !isMeetingsOpen && !isLiveNotesOpen && !isBgTasksOpen && !isEmailOpen && !isWorkspaceOpen && !isKnowledgeViewOpen && !selectedBackgroundTask && !isBrowserOpen
|
||||
const isFullScreenChat = !selectedPath && !isGraphOpen && !isSuggestedTopicsOpen && !isMeetingsOpen && !isLiveNotesOpen && !isBgTasksOpen && !isEmailOpen && !isWorkspaceOpen && !isKnowledgeViewOpen && !isChatHistoryOpen && !selectedBackgroundTask && !isBrowserOpen
|
||||
useEffect(() => {
|
||||
const handleKeyDown = (e: KeyboardEvent) => {
|
||||
if ((e.ctrlKey || e.metaKey) && e.key === 'l') {
|
||||
|
|
@ -4029,11 +4107,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 || isKnowledgeViewOpen) && isChatSidebarOpen)
|
||||
const rightPaneAvailable = Boolean((selectedPath || isGraphOpen || isSuggestedTopicsOpen || isMeetingsOpen || isLiveNotesOpen || isBgTasksOpen || isEmailOpen || isWorkspaceOpen || isKnowledgeViewOpen || isChatHistoryOpen) && isChatSidebarOpen)
|
||||
const targetPane: ShortcutPane = rightPaneAvailable
|
||||
? (isRightPaneMaximized ? 'right' : activeShortcutPane)
|
||||
: 'left'
|
||||
const inFileView = targetPane === 'left' && Boolean(selectedPath || isGraphOpen || isSuggestedTopicsOpen || isMeetingsOpen || isLiveNotesOpen || isBgTasksOpen || isEmailOpen || isWorkspaceOpen || isKnowledgeViewOpen)
|
||||
const inFileView = targetPane === 'left' && Boolean(selectedPath || isGraphOpen || isSuggestedTopicsOpen || isMeetingsOpen || isLiveNotesOpen || isBgTasksOpen || isEmailOpen || isWorkspaceOpen || isKnowledgeViewOpen || isChatHistoryOpen)
|
||||
const selectedKnowledgePath = isGraphOpen
|
||||
? GRAPH_TAB_PATH
|
||||
: isSuggestedTopicsOpen
|
||||
|
|
@ -4050,6 +4128,8 @@ function App() {
|
|||
? WORKSPACE_TAB_PATH
|
||||
: isKnowledgeViewOpen
|
||||
? KNOWLEDGE_VIEW_TAB_PATH
|
||||
: isChatHistoryOpen
|
||||
? CHAT_HISTORY_TAB_PATH
|
||||
: selectedPath
|
||||
const targetFileTabId = activeFileTabId ?? (
|
||||
selectedKnowledgePath
|
||||
|
|
@ -4104,7 +4184,7 @@ function App() {
|
|||
}
|
||||
document.addEventListener('keydown', handleTabKeyDown)
|
||||
return () => document.removeEventListener('keydown', handleTabKeyDown)
|
||||
}, [selectedPath, isGraphOpen, isSuggestedTopicsOpen, isMeetingsOpen, isLiveNotesOpen, isBgTasksOpen, isEmailOpen, isWorkspaceOpen, isKnowledgeViewOpen, isChatSidebarOpen, isRightPaneMaximized, activeShortcutPane, chatTabs, fileTabs, activeChatTabId, activeFileTabId, closeChatTab, closeFileTab, switchChatTab, switchFileTab])
|
||||
}, [selectedPath, isGraphOpen, isSuggestedTopicsOpen, isMeetingsOpen, isLiveNotesOpen, isBgTasksOpen, isEmailOpen, isWorkspaceOpen, isKnowledgeViewOpen, isChatHistoryOpen, isChatSidebarOpen, isRightPaneMaximized, activeShortcutPane, chatTabs, fileTabs, activeChatTabId, activeFileTabId, closeChatTab, closeFileTab, switchChatTab, switchFileTab])
|
||||
|
||||
const toggleExpand = (path: string, kind: 'file' | 'dir') => {
|
||||
if (kind === 'file') {
|
||||
|
|
@ -4129,7 +4209,7 @@ function App() {
|
|||
}),
|
||||
},
|
||||
}))
|
||||
if (!selectedPath && !isGraphOpen && !isSuggestedTopicsOpen && !isMeetingsOpen && !isLiveNotesOpen && !isBgTasksOpen && !isEmailOpen && !isWorkspaceOpen && !isKnowledgeViewOpen && !selectedBackgroundTask) {
|
||||
if (!selectedPath && !isGraphOpen && !isSuggestedTopicsOpen && !isMeetingsOpen && !isLiveNotesOpen && !isBgTasksOpen && !isEmailOpen && !isWorkspaceOpen && !isKnowledgeViewOpen && !isChatHistoryOpen && !selectedBackgroundTask) {
|
||||
setIsChatSidebarOpen(false)
|
||||
setIsRightPaneMaximized(false)
|
||||
}
|
||||
|
|
@ -4255,28 +4335,28 @@ function App() {
|
|||
},
|
||||
openGraph: () => {
|
||||
// From chat-only landing state, open graph directly in full knowledge view.
|
||||
if (!selectedPath && !isGraphOpen && !isSuggestedTopicsOpen && !isMeetingsOpen && !isLiveNotesOpen && !isBgTasksOpen && !isEmailOpen && !isWorkspaceOpen && !isKnowledgeViewOpen && !selectedBackgroundTask) {
|
||||
if (!selectedPath && !isGraphOpen && !isSuggestedTopicsOpen && !isMeetingsOpen && !isLiveNotesOpen && !isBgTasksOpen && !isEmailOpen && !isWorkspaceOpen && !isKnowledgeViewOpen && !isChatHistoryOpen && !selectedBackgroundTask) {
|
||||
setIsChatSidebarOpen(false)
|
||||
setIsRightPaneMaximized(false)
|
||||
}
|
||||
void navigateToView({ type: 'graph' })
|
||||
},
|
||||
openBases: () => {
|
||||
if (!selectedPath && !isGraphOpen && !isSuggestedTopicsOpen && !isMeetingsOpen && !isLiveNotesOpen && !isBgTasksOpen && !isEmailOpen && !isWorkspaceOpen && !isKnowledgeViewOpen && !selectedBackgroundTask) {
|
||||
if (!selectedPath && !isGraphOpen && !isSuggestedTopicsOpen && !isMeetingsOpen && !isLiveNotesOpen && !isBgTasksOpen && !isEmailOpen && !isWorkspaceOpen && !isKnowledgeViewOpen && !isChatHistoryOpen && !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 && !isKnowledgeViewOpen && !selectedBackgroundTask) {
|
||||
if (!selectedPath && !isGraphOpen && !isSuggestedTopicsOpen && !isMeetingsOpen && !isLiveNotesOpen && !isBgTasksOpen && !isEmailOpen && !isWorkspaceOpen && !isKnowledgeViewOpen && !isChatHistoryOpen && !selectedBackgroundTask) {
|
||||
setIsChatSidebarOpen(false)
|
||||
setIsRightPaneMaximized(false)
|
||||
}
|
||||
void navigateToView({ type: 'workspace', path })
|
||||
},
|
||||
openKnowledgeView: () => {
|
||||
if (!selectedPath && !isGraphOpen && !isSuggestedTopicsOpen && !isMeetingsOpen && !isLiveNotesOpen && !isBgTasksOpen && !isEmailOpen && !isWorkspaceOpen && !isKnowledgeViewOpen && !selectedBackgroundTask) {
|
||||
if (!selectedPath && !isGraphOpen && !isSuggestedTopicsOpen && !isMeetingsOpen && !isLiveNotesOpen && !isBgTasksOpen && !isEmailOpen && !isWorkspaceOpen && !isKnowledgeViewOpen && !isChatHistoryOpen && !selectedBackgroundTask) {
|
||||
setIsChatSidebarOpen(false)
|
||||
setIsRightPaneMaximized(false)
|
||||
}
|
||||
|
|
@ -4888,7 +4968,7 @@ function App() {
|
|||
const selectedTask = selectedBackgroundTask
|
||||
? backgroundTasks.find(t => t.name === selectedBackgroundTask)
|
||||
: null
|
||||
const isRightPaneContext = Boolean(selectedPath || isGraphOpen || isSuggestedTopicsOpen || isMeetingsOpen || isLiveNotesOpen || isBgTasksOpen || isEmailOpen || isWorkspaceOpen || isKnowledgeViewOpen || isBrowserOpen)
|
||||
const isRightPaneContext = Boolean(selectedPath || isGraphOpen || isSuggestedTopicsOpen || isMeetingsOpen || isLiveNotesOpen || isBgTasksOpen || isEmailOpen || isWorkspaceOpen || isKnowledgeViewOpen || isChatHistoryOpen || isBrowserOpen)
|
||||
const isRightPaneOnlyMode = isRightPaneContext && isChatSidebarOpen && isRightPaneMaximized
|
||||
const shouldCollapseLeftPane = isRightPaneOnlyMode
|
||||
const openMarkdownTabs = React.useMemo(() => {
|
||||
|
|
@ -4905,7 +4985,7 @@ function App() {
|
|||
return (
|
||||
<TooltipProvider delayDuration={0}>
|
||||
<SidebarSectionProvider defaultSection="tasks" onSectionChange={(section) => {
|
||||
if (section === 'knowledge' && !selectedPath && !isGraphOpen && !isSuggestedTopicsOpen && !isMeetingsOpen && !isLiveNotesOpen && !isBgTasksOpen && !isEmailOpen && !isWorkspaceOpen && !isKnowledgeViewOpen) {
|
||||
if (section === 'knowledge' && !selectedPath && !isGraphOpen && !isSuggestedTopicsOpen && !isMeetingsOpen && !isLiveNotesOpen && !isBgTasksOpen && !isEmailOpen && !isWorkspaceOpen && !isKnowledgeViewOpen && !isChatHistoryOpen) {
|
||||
void navigateToView({ type: 'file', path: BASES_DEFAULT_TAB_PATH })
|
||||
}
|
||||
}}>
|
||||
|
|
@ -4928,7 +5008,7 @@ function App() {
|
|||
onNewChat: handleNewChatTab,
|
||||
onSelectRun: (runIdToLoad) => {
|
||||
cancelRecordingIfActive()
|
||||
if (selectedPath || isGraphOpen || isSuggestedTopicsOpen || isMeetingsOpen || isLiveNotesOpen || isBgTasksOpen || isEmailOpen || isWorkspaceOpen || isKnowledgeViewOpen || isBrowserOpen) {
|
||||
if (selectedPath || isGraphOpen || isSuggestedTopicsOpen || isMeetingsOpen || isLiveNotesOpen || isBgTasksOpen || isEmailOpen || isWorkspaceOpen || isKnowledgeViewOpen || isChatHistoryOpen || isBrowserOpen) {
|
||||
setIsChatSidebarOpen(true)
|
||||
}
|
||||
|
||||
|
|
@ -4939,7 +5019,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 || isKnowledgeViewOpen || isBrowserOpen) {
|
||||
if (selectedPath || isGraphOpen || isSuggestedTopicsOpen || isMeetingsOpen || isLiveNotesOpen || isBgTasksOpen || isEmailOpen || isWorkspaceOpen || isKnowledgeViewOpen || isChatHistoryOpen || isBrowserOpen) {
|
||||
setChatTabs(prev => prev.map(t => t.id === activeChatTabId ? { ...t, runId: runIdToLoad } : t))
|
||||
loadRun(runIdToLoad)
|
||||
return
|
||||
|
|
@ -4963,14 +5043,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 || isKnowledgeViewOpen || isBrowserOpen) {
|
||||
if (selectedPath || isGraphOpen || isSuggestedTopicsOpen || isMeetingsOpen || isLiveNotesOpen || isBgTasksOpen || isEmailOpen || isWorkspaceOpen || isKnowledgeViewOpen || isChatHistoryOpen || isBrowserOpen) {
|
||||
handleNewChat()
|
||||
} else {
|
||||
void navigateToView({ type: 'chat', runId: null })
|
||||
}
|
||||
}
|
||||
} else if (runId === runIdToDelete) {
|
||||
if (selectedPath || isGraphOpen || isSuggestedTopicsOpen || isMeetingsOpen || isLiveNotesOpen || isBgTasksOpen || isEmailOpen || isWorkspaceOpen || isKnowledgeViewOpen || isBrowserOpen) {
|
||||
if (selectedPath || isGraphOpen || isSuggestedTopicsOpen || isMeetingsOpen || isLiveNotesOpen || isBgTasksOpen || isEmailOpen || isWorkspaceOpen || isKnowledgeViewOpen || isChatHistoryOpen || isBrowserOpen) {
|
||||
setChatTabs(prev => prev.map(t => t.id === activeChatTabId ? { ...t, runId: null } : t))
|
||||
handleNewChat()
|
||||
} else {
|
||||
|
|
@ -4985,6 +5065,9 @@ function App() {
|
|||
onSelectBackgroundTask: (taskName) => {
|
||||
void navigateToView({ type: 'task', name: taskName })
|
||||
},
|
||||
onOpenChatHistoryView: () => {
|
||||
void navigateToView({ type: 'chat-history' })
|
||||
},
|
||||
}}
|
||||
backgroundTasks={backgroundTasks}
|
||||
selectedBackgroundTask={selectedBackgroundTask}
|
||||
|
|
@ -5018,7 +5101,7 @@ function App() {
|
|||
canNavigateForward={canNavigateForward}
|
||||
collapsedLeftPaddingPx={collapsedLeftPaddingPx}
|
||||
>
|
||||
{(selectedPath || isGraphOpen || isSuggestedTopicsOpen || isMeetingsOpen || isLiveNotesOpen || isBgTasksOpen || isEmailOpen || isWorkspaceOpen || isKnowledgeViewOpen) && fileTabs.length >= 1 ? (
|
||||
{(selectedPath || isGraphOpen || isSuggestedTopicsOpen || isMeetingsOpen || isLiveNotesOpen || isBgTasksOpen || isEmailOpen || isWorkspaceOpen || isKnowledgeViewOpen || isChatHistoryOpen) && fileTabs.length >= 1 ? (
|
||||
<TabBar
|
||||
tabs={fileTabs}
|
||||
activeTabId={activeFileTabId ?? ''}
|
||||
|
|
@ -5026,7 +5109,7 @@ function App() {
|
|||
getTabId={(t) => t.id}
|
||||
onSwitchTab={switchFileTab}
|
||||
onCloseTab={closeFileTab}
|
||||
allowSingleTabClose={fileTabs.length === 1 && (isGraphOpen || isSuggestedTopicsOpen || isMeetingsOpen || isLiveNotesOpen || isBgTasksOpen || isEmailOpen || isWorkspaceOpen || isKnowledgeViewOpen || (selectedPath != null && isBaseFilePath(selectedPath)))}
|
||||
allowSingleTabClose={fileTabs.length === 1 && (isGraphOpen || isSuggestedTopicsOpen || isMeetingsOpen || isLiveNotesOpen || isBgTasksOpen || isEmailOpen || isWorkspaceOpen || isKnowledgeViewOpen || isChatHistoryOpen || (selectedPath != null && isBaseFilePath(selectedPath)))}
|
||||
/>
|
||||
) : (
|
||||
<TabBar
|
||||
|
|
@ -5079,7 +5162,7 @@ function App() {
|
|||
<TooltipContent side="bottom">Version history</TooltipContent>
|
||||
</Tooltip>
|
||||
)}
|
||||
{!selectedPath && !isGraphOpen && !isSuggestedTopicsOpen && !isMeetingsOpen && !isLiveNotesOpen && !isBgTasksOpen && !isEmailOpen && !isWorkspaceOpen && !isKnowledgeViewOpen && !selectedTask && !isBrowserOpen && (
|
||||
{!selectedPath && !isGraphOpen && !isSuggestedTopicsOpen && !isMeetingsOpen && !isLiveNotesOpen && !isBgTasksOpen && !isEmailOpen && !isWorkspaceOpen && !isKnowledgeViewOpen && !isChatHistoryOpen && !selectedTask && !isBrowserOpen && (
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<button
|
||||
|
|
@ -5094,7 +5177,7 @@ function App() {
|
|||
<TooltipContent side="bottom">New chat tab</TooltipContent>
|
||||
</Tooltip>
|
||||
)}
|
||||
{!selectedPath && !isGraphOpen && !isSuggestedTopicsOpen && !isMeetingsOpen && !isLiveNotesOpen && !isBgTasksOpen && !isEmailOpen && !isWorkspaceOpen && !isKnowledgeViewOpen && !isBrowserOpen && expandedFrom && (
|
||||
{!selectedPath && !isGraphOpen && !isSuggestedTopicsOpen && !isMeetingsOpen && !isLiveNotesOpen && !isBgTasksOpen && !isEmailOpen && !isWorkspaceOpen && !isKnowledgeViewOpen && !isChatHistoryOpen && !isBrowserOpen && expandedFrom && (
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<button
|
||||
|
|
@ -5109,7 +5192,7 @@ function App() {
|
|||
<TooltipContent side="bottom">Restore two-pane view</TooltipContent>
|
||||
</Tooltip>
|
||||
)}
|
||||
{(selectedPath || isGraphOpen || isSuggestedTopicsOpen || isMeetingsOpen || isLiveNotesOpen || isBgTasksOpen || isEmailOpen || isWorkspaceOpen || isKnowledgeViewOpen) && (
|
||||
{(selectedPath || isGraphOpen || isSuggestedTopicsOpen || isMeetingsOpen || isLiveNotesOpen || isBgTasksOpen || isEmailOpen || isWorkspaceOpen || isKnowledgeViewOpen || isChatHistoryOpen) && (
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<button
|
||||
|
|
@ -5204,6 +5287,26 @@ function App() {
|
|||
onVoiceNoteCreated={handleVoiceNoteCreated}
|
||||
/>
|
||||
</div>
|
||||
) : isChatHistoryOpen ? (
|
||||
<div className="flex-1 min-h-0 flex flex-col overflow-hidden">
|
||||
<ChatHistoryView
|
||||
runs={runs}
|
||||
currentRunId={runId}
|
||||
processingRunIds={processingRunIds}
|
||||
onSelectRun={(rid) => void navigateToView({ type: 'chat', runId: rid })}
|
||||
onOpenInNewTab={(rid) => openChatInNewTab(rid)}
|
||||
onDeleteRun={async (rid) => {
|
||||
try {
|
||||
await window.ipc.invoke('runs:delete', { runId: rid })
|
||||
await loadRuns()
|
||||
} catch (err) {
|
||||
console.error('Failed to delete run:', err)
|
||||
}
|
||||
}}
|
||||
onNewChat={handleNewChatTab}
|
||||
onOpenSearch={() => setIsSearchOpen(true)}
|
||||
/>
|
||||
</div>
|
||||
) : selectedPath && isBaseFilePath(selectedPath) ? (
|
||||
<div className="flex-1 min-h-0 flex flex-col overflow-hidden">
|
||||
<BasesView
|
||||
|
|
|
|||
177
apps/x/apps/renderer/src/components/chat-history-view.tsx
Normal file
177
apps/x/apps/renderer/src/components/chat-history-view.tsx
Normal file
|
|
@ -0,0 +1,177 @@
|
|||
import { useCallback, useMemo, useState } from 'react'
|
||||
import { ExternalLink, MessageSquare, SearchIcon, SquarePen, Trash2 } from 'lucide-react'
|
||||
|
||||
import { Button } from '@/components/ui/button'
|
||||
import {
|
||||
ContextMenu,
|
||||
ContextMenuContent,
|
||||
ContextMenuItem,
|
||||
ContextMenuSeparator,
|
||||
ContextMenuTrigger,
|
||||
} from '@/components/ui/context-menu'
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from '@/components/ui/dialog'
|
||||
import { formatRelativeTime } from '@/lib/relative-time'
|
||||
|
||||
type Run = {
|
||||
id: string
|
||||
title?: string
|
||||
createdAt: string
|
||||
agentId: string
|
||||
}
|
||||
|
||||
type ChatHistoryViewProps = {
|
||||
runs: Run[]
|
||||
currentRunId?: string | null
|
||||
processingRunIds?: Set<string>
|
||||
onSelectRun: (runId: string) => void
|
||||
onOpenInNewTab?: (runId: string) => void
|
||||
onDeleteRun: (runId: string) => Promise<void> | void
|
||||
onNewChat?: () => void
|
||||
onOpenSearch?: () => void
|
||||
}
|
||||
|
||||
export function ChatHistoryView({
|
||||
runs,
|
||||
currentRunId,
|
||||
processingRunIds,
|
||||
onSelectRun,
|
||||
onOpenInNewTab,
|
||||
onDeleteRun,
|
||||
onNewChat,
|
||||
onOpenSearch,
|
||||
}: ChatHistoryViewProps) {
|
||||
const [pendingDeleteId, setPendingDeleteId] = useState<string | null>(null)
|
||||
|
||||
const sortedRuns = useMemo(() => {
|
||||
return [...runs].sort((a, b) => {
|
||||
const at = new Date(a.createdAt).getTime()
|
||||
const bt = new Date(b.createdAt).getTime()
|
||||
return (Number.isNaN(bt) ? 0 : bt) - (Number.isNaN(at) ? 0 : at)
|
||||
})
|
||||
}, [runs])
|
||||
|
||||
const handleConfirmDelete = useCallback(async () => {
|
||||
if (!pendingDeleteId) return
|
||||
const id = pendingDeleteId
|
||||
setPendingDeleteId(null)
|
||||
await onDeleteRun(id)
|
||||
}, [pendingDeleteId, onDeleteRun])
|
||||
|
||||
return (
|
||||
<div className="flex h-full flex-col overflow-hidden">
|
||||
<div className="shrink-0 flex items-center justify-between gap-3 border-b border-border px-8 py-6">
|
||||
<h1 className="text-2xl font-bold tracking-tight">Chat history</h1>
|
||||
<div className="flex items-center gap-2">
|
||||
{onOpenSearch && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={onOpenSearch}
|
||||
className="inline-flex items-center gap-1.5 rounded-md border border-border bg-background px-3 py-1.5 text-sm text-foreground transition-colors hover:bg-accent"
|
||||
>
|
||||
<SearchIcon className="size-4" />
|
||||
<span>Search</span>
|
||||
</button>
|
||||
)}
|
||||
{onNewChat && (
|
||||
<Button size="sm" onClick={onNewChat}>
|
||||
<SquarePen className="size-4" />
|
||||
New chat
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex-1 overflow-y-auto">
|
||||
<div className="min-w-[480px]">
|
||||
<div className="sticky top-0 z-10 flex items-center border-b border-border bg-background px-6 py-2 text-xs font-medium text-muted-foreground">
|
||||
<div className="flex-1">Title</div>
|
||||
<div className="w-32 shrink-0">Created</div>
|
||||
</div>
|
||||
|
||||
{sortedRuns.length === 0 ? (
|
||||
<div className="px-6 py-8 text-sm text-muted-foreground">No chats yet.</div>
|
||||
) : (
|
||||
sortedRuns.map((run) => {
|
||||
const isActive = currentRunId === run.id
|
||||
const isProcessing = processingRunIds?.has(run.id)
|
||||
return (
|
||||
<ContextMenu key={run.id}>
|
||||
<ContextMenuTrigger asChild>
|
||||
<button
|
||||
type="button"
|
||||
onClick={(e) => {
|
||||
if (e.metaKey && onOpenInNewTab) {
|
||||
onOpenInNewTab(run.id)
|
||||
} else {
|
||||
onSelectRun(run.id)
|
||||
}
|
||||
}}
|
||||
className={[
|
||||
'flex w-full items-center border-b border-border/60 px-6 py-1.5 text-left text-sm transition-colors hover:bg-accent',
|
||||
isActive ? 'bg-accent/60' : '',
|
||||
].join(' ')}
|
||||
>
|
||||
<div className="flex flex-1 items-center gap-2 min-w-0">
|
||||
<MessageSquare className="size-4 shrink-0 text-muted-foreground" />
|
||||
<span className="min-w-0 truncate">{run.title || '(Untitled chat)'}</span>
|
||||
</div>
|
||||
<div className="w-32 shrink-0 text-xs text-muted-foreground tabular-nums">
|
||||
{formatRelativeTime(run.createdAt)}
|
||||
</div>
|
||||
</button>
|
||||
</ContextMenuTrigger>
|
||||
<ContextMenuContent className="w-48">
|
||||
{onOpenInNewTab && (
|
||||
<>
|
||||
<ContextMenuItem onClick={() => onOpenInNewTab(run.id)}>
|
||||
<ExternalLink className="mr-2 size-4" />
|
||||
Open in new tab
|
||||
</ContextMenuItem>
|
||||
<ContextMenuSeparator />
|
||||
</>
|
||||
)}
|
||||
{!isProcessing && (
|
||||
<ContextMenuItem
|
||||
variant="destructive"
|
||||
onClick={() => setPendingDeleteId(run.id)}
|
||||
>
|
||||
<Trash2 className="mr-2 size-4" />
|
||||
Delete
|
||||
</ContextMenuItem>
|
||||
)}
|
||||
</ContextMenuContent>
|
||||
</ContextMenu>
|
||||
)
|
||||
})
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Dialog open={!!pendingDeleteId} onOpenChange={(open) => { if (!open) setPendingDeleteId(null) }}>
|
||||
<DialogContent showCloseButton={false} className="sm:max-w-sm">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Delete chat</DialogTitle>
|
||||
<DialogDescription>
|
||||
Are you sure you want to delete this chat?
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<DialogFooter>
|
||||
<Button variant="outline" onClick={() => setPendingDeleteId(null)}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button variant="destructive" onClick={() => void handleConfirmDelete()}>
|
||||
Delete
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
@ -183,6 +183,7 @@ type TasksActions = {
|
|||
onDeleteRun: (runId: string) => void
|
||||
onOpenInNewTab?: (runId: string) => void
|
||||
onSelectBackgroundTask?: (taskName: string) => void
|
||||
onOpenChatHistoryView?: () => void
|
||||
}
|
||||
|
||||
type SidebarContentPanelProps = {
|
||||
|
|
@ -1194,62 +1195,66 @@ function TasksSection({
|
|||
</SidebarMenu>
|
||||
</>
|
||||
)}
|
||||
{runs.length > 0 && (
|
||||
<>
|
||||
<div className="px-3 py-1.5 mt-4 text-xs font-medium text-muted-foreground">
|
||||
Chat history
|
||||
</div>
|
||||
<SidebarMenu>
|
||||
{runs.map((run) => (
|
||||
<ContextMenu key={run.id}>
|
||||
<ContextMenuTrigger asChild>
|
||||
<SidebarMenuItem className="group/chat-item">
|
||||
<SidebarMenuButton
|
||||
isActive={currentRunId === run.id}
|
||||
onClick={(e) => {
|
||||
if (e.metaKey && actions?.onOpenInNewTab) {
|
||||
actions.onOpenInNewTab(run.id)
|
||||
} else {
|
||||
actions?.onSelectRun(run.id)
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div className="flex w-full items-center gap-2 min-w-0">
|
||||
<span className="min-w-0 flex-1 truncate text-sm">{run.title || '(Untitled chat)'}</span>
|
||||
{run.createdAt ? (
|
||||
<span className="shrink-0 text-[10px] text-muted-foreground">
|
||||
{formatRunTime(run.createdAt)}
|
||||
</span>
|
||||
) : null}
|
||||
</div>
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem>
|
||||
</ContextMenuTrigger>
|
||||
<ContextMenuContent className="w-48">
|
||||
{actions?.onOpenInNewTab && (
|
||||
<ContextMenuItem onClick={() => actions.onOpenInNewTab!(run.id)}>
|
||||
<ExternalLink className="mr-2 size-4" />
|
||||
Open in new tab
|
||||
</ContextMenuItem>
|
||||
)}
|
||||
{!processingRunIds?.has(run.id) && (
|
||||
<>
|
||||
{actions?.onOpenInNewTab && <ContextMenuSeparator />}
|
||||
<ContextMenuItem
|
||||
variant="destructive"
|
||||
onClick={() => setPendingDeleteRunId(run.id)}
|
||||
>
|
||||
<Trash2 className="mr-2 size-4" />
|
||||
Delete
|
||||
</ContextMenuItem>
|
||||
</>
|
||||
)}
|
||||
</ContextMenuContent>
|
||||
</ContextMenu>
|
||||
))}
|
||||
</SidebarMenu>
|
||||
</>
|
||||
)}
|
||||
<div className="px-3 py-1.5 mt-4 text-xs font-medium text-muted-foreground">
|
||||
Chat history
|
||||
</div>
|
||||
<SidebarMenu>
|
||||
{runs.slice(0, 3).map((run) => (
|
||||
<ContextMenu key={run.id}>
|
||||
<ContextMenuTrigger asChild>
|
||||
<SidebarMenuItem className="group/chat-item">
|
||||
<SidebarMenuButton
|
||||
isActive={currentRunId === run.id}
|
||||
onClick={(e) => {
|
||||
if (e.metaKey && actions?.onOpenInNewTab) {
|
||||
actions.onOpenInNewTab(run.id)
|
||||
} else {
|
||||
actions?.onSelectRun(run.id)
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div className="flex w-full items-center gap-2 min-w-0">
|
||||
<span className="min-w-0 flex-1 truncate text-sm">{run.title || '(Untitled chat)'}</span>
|
||||
{run.createdAt ? (
|
||||
<span className="shrink-0 text-[10px] text-muted-foreground">
|
||||
{formatRunTime(run.createdAt)}
|
||||
</span>
|
||||
) : null}
|
||||
</div>
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem>
|
||||
</ContextMenuTrigger>
|
||||
<ContextMenuContent className="w-48">
|
||||
{actions?.onOpenInNewTab && (
|
||||
<ContextMenuItem onClick={() => actions.onOpenInNewTab!(run.id)}>
|
||||
<ExternalLink className="mr-2 size-4" />
|
||||
Open in new tab
|
||||
</ContextMenuItem>
|
||||
)}
|
||||
{!processingRunIds?.has(run.id) && (
|
||||
<>
|
||||
{actions?.onOpenInNewTab && <ContextMenuSeparator />}
|
||||
<ContextMenuItem
|
||||
variant="destructive"
|
||||
onClick={() => setPendingDeleteRunId(run.id)}
|
||||
>
|
||||
<Trash2 className="mr-2 size-4" />
|
||||
Delete
|
||||
</ContextMenuItem>
|
||||
</>
|
||||
)}
|
||||
</ContextMenuContent>
|
||||
</ContextMenu>
|
||||
))}
|
||||
{actions?.onOpenChatHistoryView && (
|
||||
<SidebarMenuItem>
|
||||
<SidebarMenuButton onClick={() => actions.onOpenChatHistoryView?.()}>
|
||||
<ArrowUpRight className="size-4 shrink-0 text-muted-foreground" />
|
||||
<span className="text-muted-foreground">View all</span>
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem>
|
||||
)}
|
||||
</SidebarMenu>
|
||||
</SidebarGroupContent>
|
||||
|
||||
{/* Delete confirmation dialog */}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue