Skip to content

Commit e495d49

Browse files
committed
feat(chat): drag workflows and folders from sidebar into chat input
1 parent 0f602f7 commit e495d49

File tree

6 files changed

+79
-8
lines changed

6 files changed

+79
-8
lines changed

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
import { Button, Tooltip } from '@/components/emcn'
1111
import { Columns3, Eye, PanelLeft, Pencil } from '@/components/emcn/icons'
1212
import { isEphemeralResource } from '@/lib/copilot/resource-extraction'
13+
import { SIM_RESOURCE_DRAG_TYPE } from '@/lib/copilot/resource-types'
1314
import { cn } from '@/lib/core/utils/cn'
1415
import type { PreviewMode } from '@/app/workspace/[workspaceId]/files/components/file-viewer'
1516
import { AddResourceDropdown } from '@/app/workspace/[workspaceId]/home/components/mothership-view/components/add-resource-dropdown'
@@ -164,7 +165,7 @@ export function ResourceTabs({
164165
const resource = resources[idx]
165166
if (resource) {
166167
e.dataTransfer.setData(
167-
'application/x-sim-resource',
168+
SIM_RESOURCE_DRAG_TYPE,
168169
JSON.stringify({ type: resource.type, id: resource.id, title: resource.title })
169170
)
170171
}

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

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { useParams } from 'next/navigation'
66
import { Database, Folder as FolderIcon, Table as TableIcon } from '@/components/emcn/icons'
77
import { getDocumentIcon } from '@/components/icons/document-icons'
88
import { useSession } from '@/lib/auth/auth-client'
9+
import { SIM_RESOURCE_DRAG_TYPE, SIM_RESOURCES_DRAG_TYPE } from '@/lib/copilot/resource-types'
910
import { cn } from '@/lib/core/utils/cn'
1011
import { CHAT_ACCEPT_ATTRIBUTE } from '@/lib/uploads/utils/validation'
1112
import { useAvailableResources } from '@/app/workspace/[workspaceId]/home/components/mothership-view/components/add-resource-dropdown'
@@ -247,15 +248,17 @@ export function UserInput({
247248
if (textarea) {
248249
const currentValue = valueRef.current
249250
const insertAt = atInsertPosRef.current ?? textarea.selectionStart ?? currentValue.length
250-
atInsertPosRef.current = null
251-
252251
const needsSpaceBefore = insertAt > 0 && !/\s/.test(currentValue.charAt(insertAt - 1))
253252
const insertText = `${needsSpaceBefore ? ' ' : ''}@${resource.title} `
254253
const before = currentValue.slice(0, insertAt)
255254
const after = currentValue.slice(insertAt)
255+
const newValue = `${before}${insertText}${after}`
256256
const newPos = before.length + insertText.length
257257
pendingCursorRef.current = newPos
258-
setValue(`${before}${insertText}${after}`)
258+
// Eagerly sync refs so successive drop-handler iterations see the updated position
259+
valueRef.current = newValue
260+
atInsertPosRef.current = newPos
261+
setValue(newValue)
259262
}
260263

261264
const context = mapResourceToContext(resource)
@@ -281,7 +284,10 @@ export function UserInput({
281284
}, [])
282285

283286
const handleContainerDragOver = useCallback((e: React.DragEvent) => {
284-
if (e.dataTransfer.types.includes('application/x-sim-resource')) {
287+
if (
288+
e.dataTransfer.types.includes(SIM_RESOURCE_DRAG_TYPE) ||
289+
e.dataTransfer.types.includes(SIM_RESOURCES_DRAG_TYPE)
290+
) {
285291
e.preventDefault()
286292
e.stopPropagation()
287293
e.dataTransfer.dropEffect = 'copy'
@@ -292,7 +298,21 @@ export function UserInput({
292298

293299
const handleContainerDrop = useCallback(
294300
(e: React.DragEvent) => {
295-
const resourceJson = e.dataTransfer.getData('application/x-sim-resource')
301+
const resourcesJson = e.dataTransfer.getData(SIM_RESOURCES_DRAG_TYPE)
302+
if (resourcesJson) {
303+
e.preventDefault()
304+
e.stopPropagation()
305+
try {
306+
const resources = JSON.parse(resourcesJson) as MothershipResource[]
307+
for (const resource of resources) {
308+
handleResourceSelect(resource)
309+
}
310+
} catch {
311+
// Invalid JSON — ignore
312+
}
313+
return
314+
}
315+
const resourceJson = e.dataTransfer.getData(SIM_RESOURCE_DRAG_TYPE)
296316
if (resourceJson) {
297317
e.preventDefault()
298318
e.stopPropagation()

apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/workflow-list/components/folder-item/folder-item.tsx

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { createLogger } from '@sim/logger'
55
import clsx from 'clsx'
66
import { ChevronRight, Folder, FolderOpen, MoreHorizontal } from 'lucide-react'
77
import { useParams, useRouter } from 'next/navigation'
8+
import { SIM_RESOURCES_DRAG_TYPE } from '@/lib/copilot/resource-types'
89
import { generateId } from '@/lib/core/utils/uuid'
910
import { getNextWorkflowColor } from '@/lib/workflows/colors'
1011
import { useUserPermissionsContext } from '@/app/workspace/[workspaceId]/providers/workspace-permissions-provider'
@@ -18,6 +19,7 @@ import {
1819
useSidebarDragContext,
1920
} from '@/app/workspace/[workspaceId]/w/components/sidebar/hooks'
2021
import { SIDEBAR_SCROLL_EVENT } from '@/app/workspace/[workspaceId]/w/components/sidebar/sidebar'
22+
import { buildDragResources } from '@/app/workspace/[workspaceId]/w/components/sidebar/utils'
2123
import {
2224
useCanDelete,
2325
useDeleteFolder,
@@ -197,9 +199,15 @@ export function FolderItem({
197199

198200
e.dataTransfer.setData('sidebar-selection', JSON.stringify(selection))
199201
e.dataTransfer.effectAllowed = 'move'
202+
203+
const resources = buildDragResources(selection, workspaceId)
204+
if (resources.length > 0) {
205+
e.dataTransfer.setData(SIM_RESOURCES_DRAG_TYPE, JSON.stringify(resources))
206+
}
207+
200208
onDragStartProp?.()
201209
},
202-
[folder.id, onDragStartProp]
210+
[folder.id, workspaceId, onDragStartProp]
203211
)
204212

205213
const {

apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/workflow-list/components/workflow-item/workflow-item.tsx

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import clsx from 'clsx'
55
import { MoreHorizontal } from 'lucide-react'
66
import Link from 'next/link'
77
import { useParams } from 'next/navigation'
8+
import { SIM_RESOURCES_DRAG_TYPE } from '@/lib/copilot/resource-types'
89
import { useUserPermissionsContext } from '@/app/workspace/[workspaceId]/providers/workspace-permissions-provider'
910
import { getWorkflowLockToggleIds } from '@/app/workspace/[workspaceId]/w/[workflowId]/utils'
1011
import { ContextMenu } from '@/app/workspace/[workspaceId]/w/components/sidebar/components/workflow-list/components/context-menu/context-menu'
@@ -16,6 +17,7 @@ import {
1617
useItemRename,
1718
useSidebarDragContext,
1819
} from '@/app/workspace/[workspaceId]/w/components/sidebar/hooks'
20+
import { buildDragResources } from '@/app/workspace/[workspaceId]/w/components/sidebar/utils'
1921
import {
2022
useCanDelete,
2123
useDeleteSelection,
@@ -338,9 +340,15 @@ export function WorkflowItem({
338340

339341
e.dataTransfer.setData('sidebar-selection', JSON.stringify(selection))
340342
e.dataTransfer.effectAllowed = 'move'
343+
344+
const resources = buildDragResources(selection, workspaceId)
345+
if (resources.length > 0) {
346+
e.dataTransfer.setData(SIM_RESOURCES_DRAG_TYPE, JSON.stringify(resources))
347+
}
348+
341349
onDragStartProp?.()
342350
},
343-
[workflow.id, onDragStartProp]
351+
[workflow.id, workspaceId, onDragStartProp]
344352
)
345353

346354
const {

apps/sim/app/workspace/[workspaceId]/w/components/sidebar/utils.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,33 @@
1+
import type { MothershipResource } from '@/lib/copilot/resource-types'
2+
import { getFolderMap } from '@/hooks/queries/utils/folder-cache'
3+
import { getWorkflows } from '@/hooks/queries/utils/workflow-cache'
14
import type { WorkflowMetadata } from '@/stores/workflows/registry/types'
25

6+
/**
7+
* Builds a `MothershipResource` array from a sidebar drag selection so it can
8+
* be set as `application/x-sim-resources` drag data and dropped into the chat.
9+
*/
10+
export function buildDragResources(
11+
selection: { workflowIds: string[]; folderIds: string[] },
12+
workspaceId: string
13+
): MothershipResource[] {
14+
const allWorkflows = getWorkflows(workspaceId)
15+
const workflowMap = Object.fromEntries(allWorkflows.map((w) => [w.id, w]))
16+
const folderMap = getFolderMap(workspaceId)
17+
return [
18+
...selection.workflowIds.map((id) => ({
19+
type: 'workflow' as const,
20+
id,
21+
title: workflowMap[id]?.name ?? id,
22+
})),
23+
...selection.folderIds.map((id) => ({
24+
type: 'folder' as const,
25+
id,
26+
title: folderMap[id]?.name ?? id,
27+
})),
28+
]
29+
}
30+
331
export function compareByOrder<T extends { sortOrder: number; createdAt?: Date; id: string }>(
432
a: T,
533
b: T

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,9 @@ export const VFS_DIR_TO_RESOURCE: Record<string, MothershipResourceType> = {
1919
knowledgebases: 'knowledgebase',
2020
folders: 'folder',
2121
} as const
22+
23+
/** MIME type for a single dragged resource (used by resource-tabs internal reordering). */
24+
export const SIM_RESOURCE_DRAG_TYPE = 'application/x-sim-resource' as const
25+
26+
/** MIME type for an array of dragged resources (used by sidebar drag-to-chat). */
27+
export const SIM_RESOURCES_DRAG_TYPE = 'application/x-sim-resources' as const

0 commit comments

Comments
 (0)