@@ -48,6 +48,15 @@ import {
4848} from "@/components/ui/sidebar"
4949import { TooltipProvider } from "@/components/ui/tooltip"
5050import { Toaster } from "@/components/ui/sonner"
51+ import {
52+ Dialog ,
53+ DialogContent ,
54+ DialogDescription ,
55+ DialogFooter ,
56+ DialogHeader ,
57+ DialogTitle ,
58+ } from "@/components/ui/dialog"
59+ import { Input } from "@/components/ui/input"
5160import { stripKnowledgePrefix , toKnowledgePath , wikiLabel } from '@/lib/wiki-links'
5261import { OnboardingModal } from '@/components/onboarding-modal'
5362import { BackgroundTaskDetail } from '@/components/background-task-detail'
@@ -606,6 +615,10 @@ function App() {
606615 const [ tree , setTree ] = useState < TreeNode [ ] > ( [ ] )
607616 const [ expandedPaths , setExpandedPaths ] = useState < Set < string > > ( new Set ( ) )
608617 const [ recentWikiFiles , setRecentWikiFiles ] = useState < string [ ] > ( [ ] )
618+ const [ isAddVaultOpen , setIsAddVaultOpen ] = useState ( false )
619+ const [ pendingVaultPath , setPendingVaultPath ] = useState < string | null > ( null )
620+ const [ pendingVaultName , setPendingVaultName ] = useState < string > ( '' )
621+ const [ isAddingVault , setIsAddingVault ] = useState ( false )
609622 const [ isGraphOpen , setIsGraphOpen ] = useState ( false )
610623 const [ expandedFrom , setExpandedFrom ] = useState < { path : string | null ; graph : boolean } | null > ( null )
611624 const [ graphData , setGraphData ] = useState < { nodes : GraphNode [ ] ; edges : GraphEdge [ ] } > ( {
@@ -1854,6 +1867,53 @@ function App() {
18541867 }
18551868 } , [ isGraphOpen , navigateToView , runId , selectedBackgroundTask , selectedPath ] )
18561869
1870+ const deriveVaultName = useCallback ( ( vaultPath : string ) => {
1871+ const trimmed = vaultPath . replace ( / [ \\ / ] + $ / , '' )
1872+ const parts = trimmed . split ( / [ / \\ ] / )
1873+ return parts [ parts . length - 1 ] || 'Vault'
1874+ } , [ ] )
1875+
1876+ const handleAddVault = useCallback ( async ( ) => {
1877+ try {
1878+ const result = await window . ipc . invoke ( 'knowledge:pickVaultDirectory' , null )
1879+ if ( ! result . path ) return
1880+ const defaultName = deriveVaultName ( result . path )
1881+ setPendingVaultPath ( result . path )
1882+ setPendingVaultName ( defaultName )
1883+ setIsAddVaultOpen ( true )
1884+ } catch ( err ) {
1885+ console . error ( 'Failed to pick vault directory:' , err )
1886+ toast ( 'Failed to open folder picker' )
1887+ }
1888+ } , [ deriveVaultName ] )
1889+
1890+ const handleConfirmAddVault = useCallback ( async ( ) => {
1891+ if ( ! pendingVaultPath ) return
1892+ const name = pendingVaultName . trim ( )
1893+ if ( ! name ) {
1894+ toast ( 'Vault name is required' )
1895+ return
1896+ }
1897+ setIsAddingVault ( true )
1898+ try {
1899+ await window . ipc . invoke ( 'knowledge:addVault' , {
1900+ path : pendingVaultPath ,
1901+ name,
1902+ readOnly : false ,
1903+ } )
1904+ setIsAddVaultOpen ( false )
1905+ setPendingVaultPath ( null )
1906+ setPendingVaultName ( '' )
1907+ loadDirectory ( ) . then ( setTree )
1908+ toast ( `Added knowledge folder "${ name } "` )
1909+ } catch ( err ) {
1910+ console . error ( 'Failed to add vault:' , err )
1911+ toast ( 'Failed to add knowledge folder' )
1912+ } finally {
1913+ setIsAddingVault ( false )
1914+ }
1915+ } , [ loadDirectory , pendingVaultName , pendingVaultPath ] )
1916+
18571917 // Knowledge quick actions
18581918 const knowledgeFiles = React . useMemo ( ( ) => {
18591919 const files = collectFilePaths ( tree ) . filter ( ( path ) => path . endsWith ( '.md' ) )
@@ -1946,6 +2006,9 @@ function App() {
19462006 throw err
19472007 }
19482008 } ,
2009+ addVault : ( ) => {
2010+ void handleAddVault ( )
2011+ } ,
19492012 createFolder : async ( parentPath : string = 'knowledge' ) => {
19502013 try {
19512014 await window . ipc . invoke ( 'workspace:mkdir' , {
@@ -1957,6 +2020,17 @@ function App() {
19572020 throw err
19582021 }
19592022 } ,
2023+ unlinkVault : async ( mountPath : string ) => {
2024+ try {
2025+ await window . ipc . invoke ( 'knowledge:removeVault' , { nameOrMountPath : mountPath } )
2026+ if ( selectedPath && ( selectedPath === mountPath || selectedPath . startsWith ( `${ mountPath } /` ) ) ) {
2027+ setSelectedPath ( null )
2028+ }
2029+ } catch ( err ) {
2030+ console . error ( 'Failed to unlink knowledge folder:' , err )
2031+ throw err
2032+ }
2033+ } ,
19602034 openGraph : ( ) => {
19612035 void navigateToView ( { type : 'graph' } )
19622036 } ,
@@ -1989,7 +2063,7 @@ function App() {
19892063 const fullPath = workspaceRoot ? `${ workspaceRoot } /${ path } ` : path
19902064 navigator . clipboard . writeText ( fullPath )
19912065 } ,
1992- } ) , [ tree , selectedPath , workspaceRoot , collectDirPaths , navigateToFile , navigateToView ] )
2066+ } ) , [ tree , selectedPath , workspaceRoot , collectDirPaths , navigateToFile , navigateToView , handleAddVault ] )
19932067
19942068 // Handler for when a voice note is created/updated
19952069 const handleVoiceNoteCreated = useCallback ( async ( notePath : string ) => {
@@ -2569,6 +2643,50 @@ function App() {
25692643 ) }
25702644 </ div >
25712645 </ SidebarSectionProvider >
2646+ < Dialog
2647+ open = { isAddVaultOpen }
2648+ onOpenChange = { ( open ) => {
2649+ if ( ! open ) {
2650+ setIsAddVaultOpen ( false )
2651+ setPendingVaultPath ( null )
2652+ setPendingVaultName ( '' )
2653+ }
2654+ } }
2655+ >
2656+ < DialogContent >
2657+ < DialogHeader >
2658+ < DialogTitle > Add Knowledge Folder</ DialogTitle >
2659+ < DialogDescription >
2660+ Link an existing folder into knowledge.
2661+ </ DialogDescription >
2662+ </ DialogHeader >
2663+ < div className = "space-y-3" >
2664+ < div className = "text-xs text-muted-foreground break-all" >
2665+ { pendingVaultPath ?? 'No folder selected' }
2666+ </ div >
2667+ < Input
2668+ value = { pendingVaultName }
2669+ onChange = { ( event ) => setPendingVaultName ( event . target . value ) }
2670+ placeholder = "Folder name"
2671+ />
2672+ </ div >
2673+ < DialogFooter >
2674+ < Button
2675+ variant = "ghost"
2676+ onClick = { ( ) => {
2677+ setIsAddVaultOpen ( false )
2678+ setPendingVaultPath ( null )
2679+ setPendingVaultName ( '' )
2680+ } }
2681+ >
2682+ Cancel
2683+ </ Button >
2684+ < Button onClick = { handleConfirmAddVault } disabled = { isAddingVault } >
2685+ { isAddingVault ? 'Adding...' : 'Add Folder' }
2686+ </ Button >
2687+ </ DialogFooter >
2688+ </ DialogContent >
2689+ </ Dialog >
25722690 < Toaster />
25732691 < OnboardingModal
25742692 open = { showOnboarding }
0 commit comments