mirror of
https://github.com/simstudioai/sim.git
synced 2026-04-28 03:00:29 -04:00
feat(home): add folders to resource menu (#4000)
* feat(home): add folders to resource menu * fix(home): add folder to API validation and dedup logic * fix(home): add folder context processing and generic title dedup * fix(home): add folder icon to mention chip overlay * fix(home): add folder to AgentContextType and context persistence * fix(home): add workspace scoping to folder resolver, fix folderId type and dedup * user message
This commit is contained in:
@@ -15,13 +15,19 @@ 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(),
|
||||
}),
|
||||
@@ -29,7 +35,7 @@ const AddResourceSchema = z.object({
|
||||
|
||||
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(),
|
||||
})
|
||||
|
||||
@@ -37,7 +43,7 @@ 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(),
|
||||
})
|
||||
|
||||
@@ -88,6 +88,7 @@ const ChatMessageSchema = z.object({
|
||||
'docs',
|
||||
'table',
|
||||
'file',
|
||||
'folder',
|
||||
]),
|
||||
label: z.string(),
|
||||
chatId: z.string().optional(),
|
||||
@@ -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(),
|
||||
|
||||
@@ -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(),
|
||||
@@ -66,6 +66,7 @@ const MothershipMessageSchema = z.object({
|
||||
'docs',
|
||||
'table',
|
||||
'file',
|
||||
'folder',
|
||||
]),
|
||||
label: z.string(),
|
||||
chatId: z.string().optional(),
|
||||
@@ -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(),
|
||||
@@ -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 }),
|
||||
})),
|
||||
}),
|
||||
}
|
||||
|
||||
@@ -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'
|
||||
@@ -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(
|
||||
() => [
|
||||
@@ -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) => ({
|
||||
@@ -88,7 +98,7 @@ export function useAvailableResources(
|
||||
})),
|
||||
},
|
||||
],
|
||||
[workflows, tables, files, knowledgeBases, existingKeys]
|
||||
[workflows, folders, tables, files, knowledgeBases, existingKeys]
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
@@ -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'
|
||||
@@ -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: [] }} />
|
||||
@@ -172,6 +182,7 @@ export function ResourceActions({ workspaceId, resource }: ResourceActionsProps)
|
||||
return (
|
||||
<EmbeddedKnowledgeBaseActions workspaceId={workspaceId} knowledgeBaseId={resource.id} />
|
||||
)
|
||||
case 'folder':
|
||||
case 'generic':
|
||||
return null
|
||||
default:
|
||||
@@ -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)
|
||||
|
||||
@@ -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'
|
||||
@@ -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'
|
||||
@@ -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)
|
||||
@@ -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() })
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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 {
|
||||
@@ -57,6 +58,7 @@ 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>()
|
||||
@@ -64,8 +66,9 @@ function useResourceNameLookup(workspaceId: string): Map<string, string> {
|
||||
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 {
|
||||
|
||||
@@ -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 }
|
||||
}
|
||||
|
||||
@@ -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'
|
||||
@@ -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])
|
||||
@@ -663,6 +664,9 @@ export function UserInput({
|
||||
mentionIconNode = <FileDocIcon className={iconClasses} />
|
||||
break
|
||||
}
|
||||
case 'folder':
|
||||
mentionIconNode = <FolderIcon className={iconClasses} />
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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'
|
||||
@@ -81,6 +81,9 @@ function MentionHighlight({ context }: { context: ChatMessageContext }) {
|
||||
icon = <FileDocIcon className={iconClasses} />
|
||||
break
|
||||
}
|
||||
case 'folder':
|
||||
icon = <FolderIcon className={iconClasses} />
|
||||
break
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -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 }),
|
||||
}))
|
||||
}
|
||||
|
||||
@@ -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) => [
|
||||
|
||||
@@ -266,6 +266,7 @@ export interface ChatMessageContext {
|
||||
knowledgeId?: string
|
||||
tableId?: string
|
||||
fileId?: string
|
||||
folderId?: string
|
||||
}
|
||||
|
||||
export interface ChatMessage {
|
||||
|
||||
26
apps/sim/components/emcn/icons/folder.tsx
Normal file
26
apps/sim/components/emcn/icons/folder.tsx
Normal file
@@ -0,0 +1,26 @@
|
||||
import type { SVGProps } from 'react'
|
||||
|
||||
/**
|
||||
* Folder icon component
|
||||
* @param props - SVG properties including className, fill, etc.
|
||||
*/
|
||||
export function Folder(props: SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg
|
||||
width='15'
|
||||
height='13'
|
||||
viewBox='0 0 15 13'
|
||||
fill='none'
|
||||
xmlns='http://www.w3.org/2000/svg'
|
||||
aria-hidden='true'
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
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'
|
||||
fill='currentColor'
|
||||
stroke='currentColor'
|
||||
strokeWidth='0.3'
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
@@ -30,6 +30,7 @@ export { Eye } from './eye'
|
||||
export { File } from './file'
|
||||
export { FileX } from './file-x'
|
||||
export { Fingerprint } from './fingerprint'
|
||||
export { Folder } from './folder'
|
||||
export { FolderCode } from './folder-code'
|
||||
export { FolderPlus } from './folder-plus'
|
||||
export { Hammer } from './hammer'
|
||||
|
||||
@@ -48,6 +48,7 @@ export interface TaskStoredMessageContext {
|
||||
knowledgeId?: string
|
||||
tableId?: string
|
||||
fileId?: string
|
||||
folderId?: string
|
||||
}
|
||||
|
||||
export interface TaskStoredMessage {
|
||||
|
||||
@@ -33,6 +33,7 @@ export type AgentContextType =
|
||||
| 'templates'
|
||||
| 'workflow_block'
|
||||
| 'docs'
|
||||
| 'folder'
|
||||
| 'active_resource'
|
||||
|
||||
export interface AgentContext {
|
||||
@@ -178,6 +179,11 @@ export async function processContextsServer(
|
||||
if (!result) return null
|
||||
return { type: 'file', tag: ctx.label ? `@${ctx.label}` : '@', content: result.content }
|
||||
}
|
||||
if (ctx.kind === 'folder' && 'folderId' in ctx && ctx.folderId && currentWorkspaceId) {
|
||||
const result = await resolveFolderResource(ctx.folderId, currentWorkspaceId)
|
||||
if (!result) return null
|
||||
return { type: 'folder', tag: ctx.label ? `@${ctx.label}` : '@', content: result.content }
|
||||
}
|
||||
if (ctx.kind === 'docs') {
|
||||
try {
|
||||
const { searchDocumentationServerTool } = await import(
|
||||
@@ -776,6 +782,9 @@ export async function resolveActiveResourceContext(
|
||||
case 'file': {
|
||||
return await resolveFileResource(resourceId, workspaceId)
|
||||
}
|
||||
case 'folder': {
|
||||
return await resolveFolderResource(resourceId, workspaceId)
|
||||
}
|
||||
default:
|
||||
return null
|
||||
}
|
||||
@@ -812,3 +821,31 @@ async function resolveFileResource(
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
async function resolveFolderResource(
|
||||
folderId: string,
|
||||
workspaceId: string
|
||||
): Promise<AgentContext | null> {
|
||||
try {
|
||||
const { workflowFolder, workflow } = await import('@sim/db/schema')
|
||||
const [folder] = await db
|
||||
.select({ id: workflowFolder.id, name: workflowFolder.name })
|
||||
.from(workflowFolder)
|
||||
.where(and(eq(workflowFolder.id, folderId), eq(workflowFolder.workspaceId, workspaceId)))
|
||||
.limit(1)
|
||||
if (!folder) return null
|
||||
|
||||
const workflows = await db
|
||||
.select({ id: workflow.id, name: workflow.name })
|
||||
.from(workflow)
|
||||
.where(and(eq(workflow.folderId, folderId), eq(workflow.workspaceId, workspaceId)))
|
||||
|
||||
const workflowList = workflows.map((w) => `- ${w.name} (id: ${w.id})`).join('\n')
|
||||
const content = `Folder: ${folder.name} (id: ${folder.id})\nWorkflows:\n${workflowList || '(empty)'}`
|
||||
|
||||
return { type: 'active_resource', tag: '@active_resource', content }
|
||||
} catch (error) {
|
||||
logger.error('Failed to resolve folder resource', { folderId, error })
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,10 @@
|
||||
export type MothershipResourceType = 'table' | 'file' | 'workflow' | 'knowledgebase' | 'generic'
|
||||
export type MothershipResourceType =
|
||||
| 'table'
|
||||
| 'file'
|
||||
| 'workflow'
|
||||
| 'knowledgebase'
|
||||
| 'folder'
|
||||
| 'generic'
|
||||
|
||||
export interface MothershipResource {
|
||||
type: MothershipResourceType
|
||||
@@ -11,4 +17,5 @@ export const VFS_DIR_TO_RESOURCE: Record<string, MothershipResourceType> = {
|
||||
files: 'file',
|
||||
workflows: 'workflow',
|
||||
knowledgebases: 'knowledgebase',
|
||||
folders: 'folder',
|
||||
} as const
|
||||
|
||||
@@ -41,7 +41,7 @@ export async function persistChatResources(
|
||||
|
||||
const existing = Array.isArray(chat.resources) ? (chat.resources as MothershipResource[]) : []
|
||||
const map = new Map<string, MothershipResource>()
|
||||
const GENERIC = new Set(['Table', 'File', 'Workflow', 'Knowledge Base'])
|
||||
const GENERIC = new Set(['Table', 'File', 'Workflow', 'Knowledge Base', 'Folder'])
|
||||
|
||||
for (const r of existing) {
|
||||
map.set(`${r.type}:${r.id}`, r)
|
||||
|
||||
@@ -29,6 +29,7 @@ export type ChatContext =
|
||||
| { kind: 'knowledge'; knowledgeId?: string; label: string }
|
||||
| { kind: 'table'; tableId: string; label: string }
|
||||
| { kind: 'file'; fileId: string; label: string }
|
||||
| { kind: 'folder'; folderId: string; label: string }
|
||||
| { kind: 'templates'; templateId?: string; label: string }
|
||||
| { kind: 'docs'; label: string }
|
||||
| { kind: 'slash_command'; command: string; label: string }
|
||||
|
||||
Reference in New Issue
Block a user