Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 11 additions & 5 deletions apps/sim/app/api/copilot/chat/resources/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,29 +15,35 @@ import type { ChatResource, ResourceType } from '@/lib/copilot/resources'

const logger = createLogger('CopilotChatResourcesAPI')

const VALID_RESOURCE_TYPES = new Set<ResourceType>(['table', 'file', 'workflow', 'knowledgebase'])
const GENERIC_TITLES = new Set(['Table', 'File', 'Workflow', 'Knowledge Base'])
const VALID_RESOURCE_TYPES = new Set<ResourceType>([
'table',
'file',
'workflow',
'knowledgebase',
'folder',
])
const GENERIC_TITLES = new Set(['Table', 'File', 'Workflow', 'Knowledge Base', 'Folder'])

const AddResourceSchema = z.object({
chatId: z.string(),
resource: z.object({
type: z.enum(['table', 'file', 'workflow', 'knowledgebase']),
type: z.enum(['table', 'file', 'workflow', 'knowledgebase', 'folder']),
id: z.string(),
title: z.string(),
}),
})

const RemoveResourceSchema = z.object({
chatId: z.string(),
resourceType: z.enum(['table', 'file', 'workflow', 'knowledgebase']),
resourceType: z.enum(['table', 'file', 'workflow', 'knowledgebase', 'folder']),
resourceId: z.string(),
})

const ReorderResourcesSchema = z.object({
chatId: z.string(),
resources: z.array(
z.object({
type: z.enum(['table', 'file', 'workflow', 'knowledgebase']),
type: z.enum(['table', 'file', 'workflow', 'knowledgebase', 'folder']),
id: z.string(),
title: z.string(),
})
Expand Down
2 changes: 2 additions & 0 deletions apps/sim/app/api/copilot/chat/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ const ChatMessageSchema = z.object({
'docs',
'table',
'file',
'folder',
]),
label: z.string(),
chatId: z.string().optional(),
Expand All @@ -99,6 +100,7 @@ const ChatMessageSchema = z.object({
executionId: z.string().optional(),
tableId: z.string().optional(),
fileId: z.string().optional(),
folderId: z.string().optional(),
})
)
.optional(),
Expand Down
5 changes: 4 additions & 1 deletion apps/sim/app/api/mothership/chat/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ const FileAttachmentSchema = z.object({
})

const ResourceAttachmentSchema = z.object({
type: z.enum(['workflow', 'table', 'file', 'knowledgebase']),
type: z.enum(['workflow', 'table', 'file', 'knowledgebase', 'folder']),
id: z.string().min(1),
title: z.string().optional(),
active: z.boolean().optional(),
Expand Down Expand Up @@ -66,6 +66,7 @@ const MothershipMessageSchema = z.object({
'docs',
'table',
'file',
'folder',
]),
label: z.string(),
chatId: z.string().optional(),
Expand All @@ -77,6 +78,7 @@ const MothershipMessageSchema = z.object({
executionId: z.string().optional(),
tableId: z.string().optional(),
fileId: z.string().optional(),
folderId: z.string().optional(),
})
)
.optional(),
Expand Down Expand Up @@ -224,6 +226,7 @@ export async function POST(req: NextRequest) {
...(c.knowledgeId && { knowledgeId: c.knowledgeId }),
...(c.tableId && { tableId: c.tableId }),
...(c.fileId && { fileId: c.fileId }),
...(c.folderId && { folderId: c.folderId }),
})),
}),
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import type {
MothershipResource,
MothershipResourceType,
} from '@/app/workspace/[workspaceId]/home/types'
import { useFolders } from '@/hooks/queries/folders'
import { useKnowledgeBasesQuery } from '@/hooks/queries/kb/knowledge'
import { useTablesList } from '@/hooks/queries/tables'
import { useWorkflows } from '@/hooks/queries/workflows'
Expand Down Expand Up @@ -51,6 +52,7 @@ export function useAvailableResources(
const { data: tables = [] } = useTablesList(workspaceId)
const { data: files = [] } = useWorkspaceFiles(workspaceId)
const { data: knowledgeBases } = useKnowledgeBasesQuery(workspaceId)
const { data: folders = [] } = useFolders(workspaceId)

return useMemo(
() => [
Expand All @@ -63,6 +65,14 @@ export function useAvailableResources(
isOpen: existingKeys.has(`workflow:${w.id}`),
})),
},
{
type: 'folder' as const,
items: folders.map((f) => ({
id: f.id,
name: f.name,
isOpen: existingKeys.has(`folder:${f.id}`),
})),
},
{
type: 'table' as const,
items: tables.map((t) => ({
Expand All @@ -88,7 +98,7 @@ export function useAvailableResources(
})),
},
],
[workflows, tables, files, knowledgeBases, existingKeys]
[workflows, folders, tables, files, knowledgeBases, existingKeys]
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,13 @@ import { createLogger } from '@sim/logger'
import { Square } from 'lucide-react'
import { useRouter } from 'next/navigation'
import { Button, PlayOutline, Skeleton, Tooltip } from '@/components/emcn'
import { Download, FileX, SquareArrowUpRight, WorkflowX } from '@/components/emcn/icons'
import {
Download,
FileX,
Folder as FolderIcon,
SquareArrowUpRight,
WorkflowX,
} from '@/components/emcn/icons'
import {
cancelRunToolExecution,
markRunToolManuallyStopped,
Expand Down Expand Up @@ -37,6 +43,7 @@ import {
import { Table } from '@/app/workspace/[workspaceId]/tables/[tableId]/components'
import { useUsageLimits } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/hooks'
import { useWorkflowExecution } from '@/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-workflow-execution'
import { useFolders } from '@/hooks/queries/folders'
import { useWorkflows } from '@/hooks/queries/workflows'
import { useWorkspaceFiles } from '@/hooks/queries/workspace-files'
import { useSettingsNavigation } from '@/hooks/use-settings-navigation'
Expand Down Expand Up @@ -147,6 +154,9 @@ export const ResourceContent = memo(function ResourceContent({
/>
)

case 'folder':
return <EmbeddedFolder key={resource.id} workspaceId={workspaceId} folderId={resource.id} />

case 'generic':
return (
<GenericResourceContent key={resource.id} data={genericResourceData ?? { entries: [] }} />
Expand All @@ -172,6 +182,7 @@ export function ResourceActions({ workspaceId, resource }: ResourceActionsProps)
return (
<EmbeddedKnowledgeBaseActions workspaceId={workspaceId} knowledgeBaseId={resource.id} />
)
case 'folder':
case 'generic':
return null
default:
Expand Down Expand Up @@ -450,6 +461,72 @@ function EmbeddedFile({ workspaceId, fileId, previewMode, streamingContent }: Em
)
}

interface EmbeddedFolderProps {
workspaceId: string
folderId: string
}

function EmbeddedFolder({ workspaceId, folderId }: EmbeddedFolderProps) {
const { data: folderList, isPending: isFoldersPending } = useFolders(workspaceId)
const { data: workflowList = [] } = useWorkflows(workspaceId)

const folder = useMemo(
() => (folderList ?? []).find((f) => f.id === folderId),
[folderList, folderId]
)

const folderWorkflows = useMemo(
() => workflowList.filter((w) => w.folderId === folderId),
[workflowList, folderId]
)

if (isFoldersPending) return LOADING_SKELETON

if (!folder) {
return (
<div className='flex h-full flex-col items-center justify-center gap-3'>
<FolderIcon className='h-[32px] w-[32px] text-[var(--text-icon)]' />
<div className='flex flex-col items-center gap-1'>
<h2 className='font-medium text-[20px] text-[var(--text-primary)]'>Folder not found</h2>
<p className='text-[var(--text-body)] text-small'>
This folder may have been deleted or moved
</p>
</div>
</div>
)
}

return (
<div className='flex h-full flex-col overflow-y-auto p-6'>
<h2 className='mb-4 font-medium text-[16px] text-[var(--text-primary)]'>{folder.name}</h2>
{folderWorkflows.length === 0 ? (
<p className='text-[13px] text-[var(--text-muted)]'>No workflows in this folder</p>
) : (
<div className='flex flex-col gap-1'>
{folderWorkflows.map((w) => (
<button
key={w.id}
type='button'
onClick={() => window.open(`/workspace/${workspaceId}/w/${w.id}`, '_blank')}
className='flex items-center gap-2 rounded-[6px] px-3 py-2 text-left transition-colors hover:bg-[var(--surface-4)]'
>
<div
className='h-[12px] w-[12px] flex-shrink-0 rounded-[3px] border-[2px]'
style={{
backgroundColor: w.color,
borderColor: `${w.color}60`,
backgroundClip: 'padding-box',
}}
/>
<span className='truncate text-[13px] text-[var(--text-primary)]'>{w.name}</span>
</button>
))}
</div>
)}
</div>
)
}

function extractFileContent(raw: string): string {
const marker = '"content":'
const idx = raw.indexOf(marker)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { useParams } from 'next/navigation'
import {
Database,
File as FileIcon,
Folder as FolderIcon,
Table as TableIcon,
TerminalWindow,
} from '@/components/emcn/icons'
Expand All @@ -18,6 +19,7 @@ import type {
} from '@/app/workspace/[workspaceId]/home/types'
import { knowledgeKeys } from '@/hooks/queries/kb/knowledge'
import { tableKeys } from '@/hooks/queries/tables'
import { folderKeys } from '@/hooks/queries/utils/folder-keys'
import { invalidateWorkflowLists } from '@/hooks/queries/utils/invalidate-workflow-lists'
import { useWorkflows } from '@/hooks/queries/workflows'
import { workspaceFilesKeys } from '@/hooks/queries/workspace-files'
Expand Down Expand Up @@ -140,6 +142,15 @@ export const RESOURCE_REGISTRY: Record<MothershipResourceType, ResourceTypeConfi
),
renderDropdownItem: (props) => <IconDropdownItem {...props} icon={Database} />,
},
folder: {
type: 'folder',
label: 'Folders',
icon: FolderIcon,
renderTabIcon: (_resource, className) => (
<FolderIcon className={cn(className, 'text-[var(--text-icon)]')} />
),
renderDropdownItem: (props) => <IconDropdownItem {...props} icon={FolderIcon} />,
},
} as const

export const RESOURCE_TYPES = Object.values(RESOURCE_REGISTRY)
Expand Down Expand Up @@ -171,6 +182,9 @@ const RESOURCE_INVALIDATORS: Record<
qc.invalidateQueries({ queryKey: knowledgeKeys.detail(id) })
qc.invalidateQueries({ queryKey: knowledgeKeys.tagDefinitions(id) })
},
folder: (qc) => {
qc.invalidateQueries({ queryKey: folderKeys.lists() })
},
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import type {
MothershipResource,
MothershipResourceType,
} from '@/app/workspace/[workspaceId]/home/types'
import { useFolders } from '@/hooks/queries/folders'
import { useKnowledgeBasesQuery } from '@/hooks/queries/kb/knowledge'
import { useTablesList } from '@/hooks/queries/tables'
import {
Expand Down Expand Up @@ -57,15 +58,17 @@ function useResourceNameLookup(workspaceId: string): Map<string, string> {
const { data: tables = [] } = useTablesList(workspaceId)
const { data: files = [] } = useWorkspaceFiles(workspaceId)
const { data: knowledgeBases } = useKnowledgeBasesQuery(workspaceId)
const { data: folders = [] } = useFolders(workspaceId)

return useMemo(() => {
const map = new Map<string, string>()
for (const w of workflows) map.set(`workflow:${w.id}`, w.name)
for (const t of tables) map.set(`table:${t.id}`, t.name)
for (const f of files) map.set(`file:${f.id}`, f.name)
for (const kb of knowledgeBases ?? []) map.set(`knowledgebase:${kb.id}`, kb.name)
for (const folder of folders) map.set(`folder:${folder.id}`, folder.name)
return map
}, [workflows, tables, files, knowledgeBases])
}, [workflows, tables, files, knowledgeBases, folders])
}

interface ResourceTabsProps {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,8 @@ export function mapResourceToContext(resource: MothershipResource): ChatContext
return { kind: 'table', tableId: resource.id, label: resource.title }
case 'file':
return { kind: 'file', fileId: resource.id, label: resource.title }
case 'folder':
return { kind: 'folder', folderId: resource.id, label: resource.title }
default:
return { kind: 'docs', label: resource.title }
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import type React from 'react'
import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react'
import { useParams } from 'next/navigation'
import { Database, Table as TableIcon } from '@/components/emcn/icons'
import { Database, Folder as FolderIcon, Table as TableIcon } from '@/components/emcn/icons'
import { getDocumentIcon } from '@/components/icons/document-icons'
import { useSession } from '@/lib/auth/auth-client'
import { cn } from '@/lib/core/utils/cn'
Expand Down Expand Up @@ -175,6 +175,7 @@ export function UserInput({
if (ctx.kind === 'knowledge' && ctx.knowledgeId) keys.add(`knowledgebase:${ctx.knowledgeId}`)
if (ctx.kind === 'table' && ctx.tableId) keys.add(`table:${ctx.tableId}`)
if (ctx.kind === 'file' && ctx.fileId) keys.add(`file:${ctx.fileId}`)
if (ctx.kind === 'folder' && ctx.folderId) keys.add(`folder:${ctx.folderId}`)
}
return keys
}, [contextManagement.selectedContexts])
Expand Down Expand Up @@ -663,6 +664,9 @@ export function UserInput({
mentionIconNode = <FileDocIcon className={iconClasses} />
break
}
case 'folder':
mentionIconNode = <FolderIcon className={iconClasses} />
break
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import { useMemo } from 'react'
import { useParams } from 'next/navigation'
import { Database, Table as TableIcon } from '@/components/emcn/icons'
import { Database, Folder as FolderIcon, Table as TableIcon } from '@/components/emcn/icons'
import { getDocumentIcon } from '@/components/icons/document-icons'
import type { ChatMessageContext } from '@/app/workspace/[workspaceId]/home/types'
import { useWorkflows } from '@/hooks/queries/workflows'
Expand Down Expand Up @@ -81,6 +81,9 @@ function MentionHighlight({ context }: { context: ChatMessageContext }) {
icon = <FileDocIcon className={iconClasses} />
break
}
case 'folder':
icon = <FolderIcon className={iconClasses} />
break
}

return (
Expand Down
2 changes: 2 additions & 0 deletions apps/sim/app/workspace/[workspaceId]/home/hooks/use-chat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,7 @@ function mapStoredMessage(msg: TaskStoredMessage): ChatMessage {
...(c.knowledgeId && { knowledgeId: c.knowledgeId }),
...(c.tableId && { tableId: c.tableId }),
...(c.fileId && { fileId: c.fileId }),
...(c.folderId && { folderId: c.folderId }),
}))
}

Expand Down Expand Up @@ -1953,6 +1954,7 @@ export function useChat(
...('knowledgeId' in c && c.knowledgeId ? { knowledgeId: c.knowledgeId } : {}),
...('tableId' in c && c.tableId ? { tableId: c.tableId } : {}),
...('fileId' in c && c.fileId ? { fileId: c.fileId } : {}),
...('folderId' in c && c.folderId ? { folderId: c.folderId } : {}),
}))

setMessages((prev) => [
Expand Down
1 change: 1 addition & 0 deletions apps/sim/app/workspace/[workspaceId]/home/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,7 @@ export interface ChatMessageContext {
knowledgeId?: string
tableId?: string
fileId?: string
folderId?: string
}

export interface ChatMessage {
Expand Down
Loading
Loading