@@ -45,6 +45,15 @@ import {
4545} from "@/components/ui/sidebar"
4646import { Tooltip , TooltipContent , TooltipProvider , TooltipTrigger } from "@/components/ui/tooltip"
4747import { Toaster } from "@/components/ui/sonner"
48+ import {
49+ Dialog ,
50+ DialogContent ,
51+ DialogDescription ,
52+ DialogFooter ,
53+ DialogHeader ,
54+ DialogTitle ,
55+ } from "@/components/ui/dialog"
56+ import { Input } from "@/components/ui/input"
4857import { stripKnowledgePrefix , toKnowledgePath , wikiLabel } from '@/lib/wiki-links'
4958import { OnboardingModal } from '@/components/onboarding-modal'
5059import { SearchDialog } from '@/components/search-dialog'
@@ -356,6 +365,10 @@ function App() {
356365 const [ tree , setTree ] = useState < TreeNode [ ] > ( [ ] )
357366 const [ expandedPaths , setExpandedPaths ] = useState < Set < string > > ( new Set ( ) )
358367 const [ recentWikiFiles , setRecentWikiFiles ] = useState < string [ ] > ( [ ] )
368+ const [ isAddVaultOpen , setIsAddVaultOpen ] = useState ( false )
369+ const [ pendingVaultPath , setPendingVaultPath ] = useState < string | null > ( null )
370+ const [ pendingVaultName , setPendingVaultName ] = useState < string > ( '' )
371+ const [ isAddingVault , setIsAddingVault ] = useState ( false )
359372 const [ isGraphOpen , setIsGraphOpen ] = useState ( false )
360373 const [ expandedFrom , setExpandedFrom ] = useState < { path : string | null ; graph : boolean } | null > ( null )
361374 const [ graphData , setGraphData ] = useState < { nodes : GraphNode [ ] ; edges : GraphEdge [ ] } > ( {
@@ -2186,6 +2199,62 @@ function App() {
21862199 setExpandedPaths ( newExpanded )
21872200 }
21882201
2202+ // Handle sidebar section changes - switch to chat view for tasks
2203+ const handleSectionChange = useCallback ( ( section : ActiveSection ) => {
2204+ if ( section === 'tasks' ) {
2205+ if ( selectedBackgroundTask ) return
2206+ if ( selectedPath || isGraphOpen ) {
2207+ void navigateToView ( { type : 'chat' , runId } )
2208+ }
2209+ }
2210+ } , [ isGraphOpen , navigateToView , runId , selectedBackgroundTask , selectedPath ] )
2211+
2212+ const deriveVaultName = useCallback ( ( vaultPath : string ) => {
2213+ const trimmed = vaultPath . replace ( / [ \\ / ] + $ / , '' )
2214+ const parts = trimmed . split ( / [ / \\ ] / )
2215+ return parts [ parts . length - 1 ] || 'Folder'
2216+ } , [ ] )
2217+
2218+ const handleAddVault = useCallback ( async ( ) => {
2219+ try {
2220+ const result = await window . ipc . invoke ( 'knowledge:pickVaultDirectory' , null )
2221+ if ( ! result . path ) return
2222+ const defaultName = deriveVaultName ( result . path )
2223+ setPendingVaultPath ( result . path )
2224+ setPendingVaultName ( defaultName )
2225+ setIsAddVaultOpen ( true )
2226+ } catch ( err ) {
2227+ console . error ( 'Failed to pick vault directory:' , err )
2228+ toast ( 'Failed to open folder picker' )
2229+ }
2230+ } , [ deriveVaultName ] )
2231+
2232+ const handleConfirmAddVault = useCallback ( async ( ) => {
2233+ if ( ! pendingVaultPath ) return
2234+ const name = pendingVaultName . trim ( )
2235+ if ( ! name ) {
2236+ toast ( 'Folder name is required' )
2237+ return
2238+ }
2239+ setIsAddingVault ( true )
2240+ try {
2241+ await window . ipc . invoke ( 'knowledge:addVault' , {
2242+ path : pendingVaultPath ,
2243+ name,
2244+ readOnly : false ,
2245+ } )
2246+ setIsAddVaultOpen ( false )
2247+ setPendingVaultPath ( null )
2248+ setPendingVaultName ( '' )
2249+ loadDirectory ( ) . then ( setTree )
2250+ toast ( `Added knowledge folder "${ name } "` )
2251+ } catch ( err ) {
2252+ console . error ( 'Failed to add vault:' , err )
2253+ toast ( 'Failed to add knowledge folder' )
2254+ } finally {
2255+ setIsAddingVault ( false )
2256+ }
2257+ } , [ loadDirectory , pendingVaultName , pendingVaultPath ] )
21892258 // Knowledge quick actions
21902259 const knowledgeFiles = React . useMemo ( ( ) => {
21912260 const files = collectFilePaths ( tree ) . filter ( ( path ) => path . endsWith ( '.md' ) )
@@ -2278,6 +2347,9 @@ function App() {
22782347 throw err
22792348 }
22802349 } ,
2350+ addVault : ( ) => {
2351+ void handleAddVault ( )
2352+ } ,
22812353 createFolder : async ( parentPath : string = 'knowledge' ) => {
22822354 try {
22832355 await window . ipc . invoke ( 'workspace:mkdir' , {
@@ -2289,6 +2361,17 @@ function App() {
22892361 throw err
22902362 }
22912363 } ,
2364+ unlinkVault : async ( mountPath : string ) => {
2365+ try {
2366+ await window . ipc . invoke ( 'knowledge:removeVault' , { nameOrMountPath : mountPath } )
2367+ if ( selectedPath && ( selectedPath === mountPath || selectedPath . startsWith ( `${ mountPath } /` ) ) ) {
2368+ setSelectedPath ( null )
2369+ }
2370+ } catch ( err ) {
2371+ console . error ( 'Failed to unlink knowledge folder:' , err )
2372+ throw err
2373+ }
2374+ } ,
22922375 openGraph : ( ) => {
22932376 void navigateToView ( { type : 'graph' } )
22942377 } ,
@@ -2355,7 +2438,19 @@ function App() {
23552438 onOpenInNewTab : ( path : string ) => {
23562439 openFileInNewTab ( path )
23572440 } ,
2358- } ) , [ tree , selectedPath , workspaceRoot , navigateToFile , navigateToView , openFileInNewTab , fileTabs , closeFileTab , removeEditorCacheForPath ] )
2441+ } ) , [
2442+ tree ,
2443+ selectedPath ,
2444+ workspaceRoot ,
2445+ collectDirPaths ,
2446+ navigateToFile ,
2447+ navigateToView ,
2448+ handleAddVault ,
2449+ openFileInNewTab ,
2450+ fileTabs ,
2451+ closeFileTab ,
2452+ removeEditorCacheForPath ,
2453+ ] )
23592454
23602455 // Handler for when a voice note is created/updated
23612456 const handleVoiceNoteCreated = useCallback ( async ( notePath : string ) => {
@@ -3089,6 +3184,50 @@ function App() {
30893184 onSelectRun = { ( id ) => { void navigateToView ( { type : 'chat' , runId : id } ) } }
30903185 />
30913186 </ SidebarSectionProvider >
3187+ < Dialog
3188+ open = { isAddVaultOpen }
3189+ onOpenChange = { ( open ) => {
3190+ if ( ! open ) {
3191+ setIsAddVaultOpen ( false )
3192+ setPendingVaultPath ( null )
3193+ setPendingVaultName ( '' )
3194+ }
3195+ } }
3196+ >
3197+ < DialogContent >
3198+ < DialogHeader >
3199+ < DialogTitle > Add Knowledge Folder</ DialogTitle >
3200+ < DialogDescription >
3201+ Link an existing folder into knowledge.
3202+ </ DialogDescription >
3203+ </ DialogHeader >
3204+ < div className = "space-y-3" >
3205+ < div className = "text-xs text-muted-foreground break-all" >
3206+ { pendingVaultPath ?? 'No folder selected' }
3207+ </ div >
3208+ < Input
3209+ value = { pendingVaultName }
3210+ onChange = { ( event ) => setPendingVaultName ( event . target . value ) }
3211+ placeholder = "Folder name"
3212+ />
3213+ </ div >
3214+ < DialogFooter >
3215+ < Button
3216+ variant = "ghost"
3217+ onClick = { ( ) => {
3218+ setIsAddVaultOpen ( false )
3219+ setPendingVaultPath ( null )
3220+ setPendingVaultName ( '' )
3221+ } }
3222+ >
3223+ Cancel
3224+ </ Button >
3225+ < Button onClick = { handleConfirmAddVault } disabled = { isAddingVault } >
3226+ { isAddingVault ? 'Adding...' : 'Add Folder' }
3227+ </ Button >
3228+ </ DialogFooter >
3229+ </ DialogContent >
3230+ </ Dialog >
30923231 < Toaster />
30933232 < OnboardingModal
30943233 open = { showOnboarding }
0 commit comments