Skip to content

Commit 6a86b9f

Browse files
committed
feat(home): add folders to resource menu
1 parent 25b4a3f commit 6a86b9f

File tree

10 files changed

+146
-4
lines changed

10 files changed

+146
-4
lines changed

apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/components/add-resource-dropdown/add-resource-dropdown.tsx

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import type {
2424
MothershipResource,
2525
MothershipResourceType,
2626
} from '@/app/workspace/[workspaceId]/home/types'
27+
import { useFolders } from '@/hooks/queries/folders'
2728
import { useKnowledgeBasesQuery } from '@/hooks/queries/kb/knowledge'
2829
import { useTablesList } from '@/hooks/queries/tables'
2930
import { useWorkflows } from '@/hooks/queries/workflows'
@@ -51,6 +52,7 @@ export function useAvailableResources(
5152
const { data: tables = [] } = useTablesList(workspaceId)
5253
const { data: files = [] } = useWorkspaceFiles(workspaceId)
5354
const { data: knowledgeBases } = useKnowledgeBasesQuery(workspaceId)
55+
const { data: folders = [] } = useFolders(workspaceId)
5456

5557
return useMemo(
5658
() => [
@@ -63,6 +65,14 @@ export function useAvailableResources(
6365
isOpen: existingKeys.has(`workflow:${w.id}`),
6466
})),
6567
},
68+
{
69+
type: 'folder' as const,
70+
items: folders.map((f) => ({
71+
id: f.id,
72+
name: f.name,
73+
isOpen: existingKeys.has(`folder:${f.id}`),
74+
})),
75+
},
6676
{
6777
type: 'table' as const,
6878
items: tables.map((t) => ({
@@ -88,7 +98,7 @@ export function useAvailableResources(
8898
})),
8999
},
90100
],
91-
[workflows, tables, files, knowledgeBases, existingKeys]
101+
[workflows, folders, tables, files, knowledgeBases, existingKeys]
92102
)
93103
}
94104

apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/components/resource-content/resource-content.tsx

Lines changed: 78 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,13 @@ import { createLogger } from '@sim/logger'
55
import { Square } from 'lucide-react'
66
import { useRouter } from 'next/navigation'
77
import { Button, PlayOutline, Skeleton, Tooltip } from '@/components/emcn'
8-
import { Download, FileX, SquareArrowUpRight, WorkflowX } from '@/components/emcn/icons'
8+
import {
9+
Download,
10+
FileX,
11+
Folder as FolderIcon,
12+
SquareArrowUpRight,
13+
WorkflowX,
14+
} from '@/components/emcn/icons'
915
import {
1016
cancelRunToolExecution,
1117
markRunToolManuallyStopped,
@@ -37,6 +43,7 @@ import {
3743
import { Table } from '@/app/workspace/[workspaceId]/tables/[tableId]/components'
3844
import { useUsageLimits } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/hooks'
3945
import { useWorkflowExecution } from '@/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-workflow-execution'
46+
import { useFolders } from '@/hooks/queries/folders'
4047
import { useWorkflows } from '@/hooks/queries/workflows'
4148
import { useWorkspaceFiles } from '@/hooks/queries/workspace-files'
4249
import { useSettingsNavigation } from '@/hooks/use-settings-navigation'
@@ -147,6 +154,9 @@ export const ResourceContent = memo(function ResourceContent({
147154
/>
148155
)
149156

157+
case 'folder':
158+
return <EmbeddedFolder key={resource.id} workspaceId={workspaceId} folderId={resource.id} />
159+
150160
case 'generic':
151161
return (
152162
<GenericResourceContent key={resource.id} data={genericResourceData ?? { entries: [] }} />
@@ -172,6 +182,7 @@ export function ResourceActions({ workspaceId, resource }: ResourceActionsProps)
172182
return (
173183
<EmbeddedKnowledgeBaseActions workspaceId={workspaceId} knowledgeBaseId={resource.id} />
174184
)
185+
case 'folder':
175186
case 'generic':
176187
return null
177188
default:
@@ -450,6 +461,72 @@ function EmbeddedFile({ workspaceId, fileId, previewMode, streamingContent }: Em
450461
)
451462
}
452463

464+
interface EmbeddedFolderProps {
465+
workspaceId: string
466+
folderId: string
467+
}
468+
469+
function EmbeddedFolder({ workspaceId, folderId }: EmbeddedFolderProps) {
470+
const { data: folderList, isPending: isFoldersPending } = useFolders(workspaceId)
471+
const { data: workflowList = [] } = useWorkflows(workspaceId)
472+
473+
const folder = useMemo(
474+
() => (folderList ?? []).find((f) => f.id === folderId),
475+
[folderList, folderId]
476+
)
477+
478+
const folderWorkflows = useMemo(
479+
() => workflowList.filter((w) => w.folderId === folderId),
480+
[workflowList, folderId]
481+
)
482+
483+
if (isFoldersPending) return LOADING_SKELETON
484+
485+
if (!folder) {
486+
return (
487+
<div className='flex h-full flex-col items-center justify-center gap-3'>
488+
<FolderIcon className='h-[32px] w-[32px] text-[var(--text-icon)]' />
489+
<div className='flex flex-col items-center gap-1'>
490+
<h2 className='font-medium text-[20px] text-[var(--text-primary)]'>Folder not found</h2>
491+
<p className='text-[var(--text-body)] text-small'>
492+
This folder may have been deleted or moved
493+
</p>
494+
</div>
495+
</div>
496+
)
497+
}
498+
499+
return (
500+
<div className='flex h-full flex-col overflow-y-auto p-6'>
501+
<h2 className='mb-4 font-medium text-[16px] text-[var(--text-primary)]'>{folder.name}</h2>
502+
{folderWorkflows.length === 0 ? (
503+
<p className='text-[13px] text-[var(--text-muted)]'>No workflows in this folder</p>
504+
) : (
505+
<div className='flex flex-col gap-1'>
506+
{folderWorkflows.map((w) => (
507+
<button
508+
key={w.id}
509+
type='button'
510+
onClick={() => window.open(`/workspace/${workspaceId}/w/${w.id}`, '_blank')}
511+
className='flex items-center gap-2 rounded-[6px] px-3 py-2 text-left transition-colors hover:bg-[var(--surface-4)]'
512+
>
513+
<div
514+
className='h-[12px] w-[12px] flex-shrink-0 rounded-[3px] border-[2px]'
515+
style={{
516+
backgroundColor: w.color,
517+
borderColor: `${w.color}60`,
518+
backgroundClip: 'padding-box',
519+
}}
520+
/>
521+
<span className='truncate text-[13px] text-[var(--text-primary)]'>{w.name}</span>
522+
</button>
523+
))}
524+
</div>
525+
)}
526+
</div>
527+
)
528+
}
529+
453530
function extractFileContent(raw: string): string {
454531
const marker = '"content":'
455532
const idx = raw.indexOf(marker)

apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/components/resource-registry/resource-registry.tsx

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { useParams } from 'next/navigation'
66
import {
77
Database,
88
File as FileIcon,
9+
Folder as FolderIcon,
910
Table as TableIcon,
1011
TerminalWindow,
1112
} from '@/components/emcn/icons'
@@ -18,6 +19,7 @@ import type {
1819
} from '@/app/workspace/[workspaceId]/home/types'
1920
import { knowledgeKeys } from '@/hooks/queries/kb/knowledge'
2021
import { tableKeys } from '@/hooks/queries/tables'
22+
import { folderKeys } from '@/hooks/queries/utils/folder-keys'
2123
import { invalidateWorkflowLists } from '@/hooks/queries/utils/invalidate-workflow-lists'
2224
import { useWorkflows } from '@/hooks/queries/workflows'
2325
import { workspaceFilesKeys } from '@/hooks/queries/workspace-files'
@@ -140,6 +142,15 @@ export const RESOURCE_REGISTRY: Record<MothershipResourceType, ResourceTypeConfi
140142
),
141143
renderDropdownItem: (props) => <IconDropdownItem {...props} icon={Database} />,
142144
},
145+
folder: {
146+
type: 'folder',
147+
label: 'Folders',
148+
icon: FolderIcon,
149+
renderTabIcon: (_resource, className) => (
150+
<FolderIcon className={cn(className, 'text-[var(--text-icon)]')} />
151+
),
152+
renderDropdownItem: (props) => <IconDropdownItem {...props} icon={FolderIcon} />,
153+
},
143154
} as const
144155

145156
export const RESOURCE_TYPES = Object.values(RESOURCE_REGISTRY)
@@ -171,6 +182,9 @@ const RESOURCE_INVALIDATORS: Record<
171182
qc.invalidateQueries({ queryKey: knowledgeKeys.detail(id) })
172183
qc.invalidateQueries({ queryKey: knowledgeKeys.tagDefinitions(id) })
173184
},
185+
folder: (qc) => {
186+
qc.invalidateQueries({ queryKey: folderKeys.lists() })
187+
},
174188
}
175189

176190
/**

apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/components/resource-tabs/resource-tabs.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import type {
2323
MothershipResource,
2424
MothershipResourceType,
2525
} from '@/app/workspace/[workspaceId]/home/types'
26+
import { useFolders } from '@/hooks/queries/folders'
2627
import { useKnowledgeBasesQuery } from '@/hooks/queries/kb/knowledge'
2728
import { useTablesList } from '@/hooks/queries/tables'
2829
import {
@@ -57,15 +58,17 @@ function useResourceNameLookup(workspaceId: string): Map<string, string> {
5758
const { data: tables = [] } = useTablesList(workspaceId)
5859
const { data: files = [] } = useWorkspaceFiles(workspaceId)
5960
const { data: knowledgeBases } = useKnowledgeBasesQuery(workspaceId)
61+
const { data: folders = [] } = useFolders(workspaceId)
6062

6163
return useMemo(() => {
6264
const map = new Map<string, string>()
6365
for (const w of workflows) map.set(`workflow:${w.id}`, w.name)
6466
for (const t of tables) map.set(`table:${t.id}`, t.name)
6567
for (const f of files) map.set(`file:${f.id}`, f.name)
6668
for (const kb of knowledgeBases ?? []) map.set(`knowledgebase:${kb.id}`, kb.name)
69+
for (const folder of folders) map.set(`folder:${folder.id}`, folder.name)
6770
return map
68-
}, [workflows, tables, files, knowledgeBases])
71+
}, [workflows, tables, files, knowledgeBases, folders])
6972
}
7073

7174
interface ResourceTabsProps {

apps/sim/app/workspace/[workspaceId]/home/components/user-input/components/constants.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,8 @@ export function mapResourceToContext(resource: MothershipResource): ChatContext
8787
return { kind: 'table', tableId: resource.id, label: resource.title }
8888
case 'file':
8989
return { kind: 'file', fileId: resource.id, label: resource.title }
90+
case 'folder':
91+
return { kind: 'folder', folderId: resource.id, label: resource.title }
9092
default:
9193
return { kind: 'docs', label: resource.title }
9294
}

apps/sim/app/workspace/[workspaceId]/home/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,7 @@ export interface ChatMessageContext {
266266
knowledgeId?: string
267267
tableId?: string
268268
fileId?: string
269+
folderId?: string
269270
}
270271

271272
export interface ChatMessage {
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import type { SVGProps } from 'react'
2+
3+
/**
4+
* Folder icon component
5+
* @param props - SVG properties including className, fill, etc.
6+
*/
7+
export function Folder(props: SVGProps<SVGSVGElement>) {
8+
return (
9+
<svg
10+
width='15'
11+
height='13'
12+
viewBox='0 0 15 13'
13+
fill='none'
14+
xmlns='http://www.w3.org/2000/svg'
15+
aria-hidden='true'
16+
{...props}
17+
>
18+
<path
19+
d='M4.32234e-07 5.83339V3.79628C4.32234e-07 3.19982 -0.000206684 2.71995 0.0338546 2.33339C0.0685083 1.94027 0.141749 1.59614 0.317058 1.28196C0.542977 0.877129 0.87707 0.543036 1.2819 0.317117C1.59608 0.141808 1.94021 0.0685674 2.33333 0.0339137C2.71989 -0.000147559 3.19976 5.9557e-05 3.79622 5.9557e-05C4.53268 5.9264e-05 5.03054 -0.0078558 5.47526 0.158914C6.46893 0.531571 6.86678 1.44909 7.19141 2.09837L7.47591 2.66673H10.3333C11.025 2.66673 11.5814 2.66637 12.0267 2.71165C12.4803 2.75779 12.874 2.85548 13.222 3.08795C13.495 3.27035 13.7297 3.50508 13.9121 3.77805C14.1446 4.12607 14.2423 4.51976 14.2884 4.97337C14.3337 5.41867 14.3333 5.97505 14.3333 6.66673C14.3333 7.82671 14.3338 8.73433 14.2604 9.45579C14.1862 10.1855 14.0323 10.7801 13.6875 11.2963C13.4078 11.7148 13.0481 12.0746 12.6296 12.3542C12.1134 12.6991 11.5188 12.8529 10.7891 12.9271C10.0676 13.0005 9.15998 13.0001 8 13.0001H7.16667C5.6096 13.0001 4.39144 13.0013 3.44271 12.8738C2.47955 12.7443 1.71959 12.4736 1.12305 11.877C0.526507 11.2805 0.255796 10.5205 0.126303 9.55735C-0.00122168 8.60861 4.32234e-07 7.39046 4.32234e-07 5.83339ZM1 5.83339C1 7.41888 1.00132 8.55789 1.11784 9.42454C1.23243 10.2767 1.45034 10.7902 1.83008 11.17C2.20982 11.5497 2.72339 11.7676 3.57552 11.8822C4.44217 11.9987 5.58118 12.0001 7.16667 12.0001H8C9.18079 12.0001 10.029 11.9994 10.6882 11.9324C11.3387 11.8661 11.7498 11.7396 12.0742 11.5228C12.3836 11.3161 12.6494 11.0503 12.8561 10.7409C13.0729 10.4165 13.1994 10.0054 13.2656 9.35488C13.3327 8.69577 13.3333 7.84752 13.3333 6.66673C13.3333 5.9541 13.3326 5.45727 13.2936 5.07428C13.2555 4.69972 13.1852 4.48976 13.0807 4.33339C12.9713 4.16961 12.8305 4.02877 12.6667 3.91933C12.5103 3.81488 12.3003 3.74454 11.9258 3.70644C11.5428 3.66748 11.046 3.66673 10.3333 3.66673H5.16667C4.89052 3.66673 4.66667 3.44287 4.66667 3.16673C4.66667 2.89058 4.89052 2.66673 5.16667 2.66673H6.35742L6.29688 2.54563C5.92188 1.79565 5.68045 1.30454 5.1237 1.09576C4.88932 1.00791 4.6112 1.00006 3.79622 1.00006C3.18196 1.00006 2.75368 1.00072 2.42122 1.03001C2.09531 1.05874 1.90901 1.11196 1.76888 1.19016C1.52605 1.3257 1.32564 1.52611 1.1901 1.76894C1.1119 1.90907 1.05868 2.09537 1.02995 2.42128C1.00066 2.75373 1 3.18202 1 3.79628V5.83339Z'
20+
fill='currentColor'
21+
stroke='currentColor'
22+
strokeWidth='0.3'
23+
/>
24+
</svg>
25+
)
26+
}

apps/sim/components/emcn/icons/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ export { Eye } from './eye'
3030
export { File } from './file'
3131
export { FileX } from './file-x'
3232
export { Fingerprint } from './fingerprint'
33+
export { Folder } from './folder'
3334
export { FolderCode } from './folder-code'
3435
export { FolderPlus } from './folder-plus'
3536
export { Hammer } from './hammer'

apps/sim/lib/copilot/resource-types.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
1-
export type MothershipResourceType = 'table' | 'file' | 'workflow' | 'knowledgebase' | 'generic'
1+
export type MothershipResourceType =
2+
| 'table'
3+
| 'file'
4+
| 'workflow'
5+
| 'knowledgebase'
6+
| 'folder'
7+
| 'generic'
28

39
export interface MothershipResource {
410
type: MothershipResourceType
@@ -11,4 +17,5 @@ export const VFS_DIR_TO_RESOURCE: Record<string, MothershipResourceType> = {
1117
files: 'file',
1218
workflows: 'workflow',
1319
knowledgebases: 'knowledgebase',
20+
folders: 'folder',
1421
} as const

apps/sim/stores/panel/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ export type ChatContext =
2929
| { kind: 'knowledge'; knowledgeId?: string; label: string }
3030
| { kind: 'table'; tableId: string; label: string }
3131
| { kind: 'file'; fileId: string; label: string }
32+
| { kind: 'folder'; folderId: string; label: string }
3233
| { kind: 'templates'; templateId?: string; label: string }
3334
| { kind: 'docs'; label: string }
3435
| { kind: 'slash_command'; command: string; label: string }

0 commit comments

Comments
 (0)