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
15 changes: 14 additions & 1 deletion apps/sim/app/api/mothership/chats/[chatId]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ export const PATCH = withRouteHandler(
const parsed = await parseRequest(updateMothershipChatContract, request, context)
if (!parsed.success) return parsed.response
const { chatId } = parsed.data.params
const { title, isUnread } = parsed.data.body
const { title, isUnread, pinned } = parsed.data.body

const updates: Record<string, unknown> = {}

Expand All @@ -157,6 +157,9 @@ export const PATCH = withRouteHandler(
if (isUnread !== undefined) {
updates.lastSeenAt = isUnread ? null : sql`GREATEST(${copilotChats.updatedAt}, NOW())`
}
if (pinned !== undefined) {
updates.pinned = pinned
}

const [updatedChat] = await db
.update(copilotChats)
Expand Down Expand Up @@ -203,6 +206,16 @@ export const PATCH = withRouteHandler(
}
)
}
if (pinned !== undefined) {
captureServerEvent(
userId,
pinned ? 'task_pinned' : 'task_unpinned',
{ workspace_id: updatedChat.workspaceId },
{
groups: { workspace: updatedChat.workspaceId },
}
)
}
}

return NextResponse.json({ success: true })
Expand Down
3 changes: 2 additions & 1 deletion apps/sim/app/api/mothership/chats/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ export const GET = withRouteHandler(async (request: NextRequest) => {
updatedAt: copilotChats.updatedAt,
activeStreamId: copilotChats.conversationId,
lastSeenAt: copilotChats.lastSeenAt,
pinned: copilotChats.pinned,
})
.from(copilotChats)
.where(
Expand All @@ -54,7 +55,7 @@ export const GET = withRouteHandler(async (request: NextRequest) => {
eq(copilotChats.type, 'mothership')
)
)
.orderBy(desc(copilotChats.updatedAt))
.orderBy(desc(copilotChats.pinned), desc(copilotChats.updatedAt))

const streamMarkers = await reconcileChatStreamMarkers(
chats.map((c) => ({ chatId: c.id, streamId: c.activeStreamId })),
Expand Down
2 changes: 2 additions & 0 deletions apps/sim/app/api/mothership/execute/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ export const POST = withRouteHandler(async (req: NextRequest) => {
chatId,
messageId: providedMessageId,
requestId: providedRequestId,
fileAttachments,
workflowId,
executionId,
} = validation.data.body
Expand Down Expand Up @@ -89,6 +90,7 @@ export const POST = withRouteHandler(async (req: NextRequest) => {
messageId,
isHosted: true,
workspaceContext: workspaceContextWithMothershipTools,
...(fileAttachments && fileAttachments.length > 0 ? { fileAttachments } : {}),
...(integrationTools.length > 0 ? { integrationTools } : {}),
...(mothershipToolRuntime.tools.length > 0
? { mothershipTools: mothershipToolRuntime.tools }
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
'use client'

import { type RefObject, useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { Pin, PinOff } from 'lucide-react'
import {
Button,
DropdownMenu,
Expand Down Expand Up @@ -234,6 +235,7 @@ interface ContextMenuProps {
onOpenInNewTab?: () => void
onMarkAsRead?: () => void
onMarkAsUnread?: () => void
onTogglePin?: () => void
onRename?: () => void
onCreate?: () => void
onCreateFolder?: () => void
Expand All @@ -245,6 +247,8 @@ interface ContextMenuProps {
showOpenInNewTab?: boolean
showMarkAsRead?: boolean
showMarkAsUnread?: boolean
showPin?: boolean
isPinned?: boolean
showRename?: boolean
showCreate?: boolean
showCreateFolder?: boolean
Expand Down Expand Up @@ -288,6 +292,7 @@ export function ContextMenu({
onOpenInNewTab,
onMarkAsRead,
onMarkAsUnread,
onTogglePin,
onRename,
onCreate,
onCreateFolder,
Expand All @@ -299,6 +304,8 @@ export function ContextMenu({
showOpenInNewTab = false,
showMarkAsRead = false,
showMarkAsUnread = false,
showPin = false,
isPinned = false,
showRename = true,
showCreate = false,
showCreateFolder = false,
Expand Down Expand Up @@ -375,7 +382,10 @@ export function ContextMenu({
}, [])

const hasNavigationSection = showOpenInNewTab && onOpenInNewTab
const hasStatusSection = (showMarkAsRead && onMarkAsRead) || (showMarkAsUnread && onMarkAsUnread)
const hasStatusSection =
(showMarkAsRead && onMarkAsRead) ||
(showMarkAsUnread && onMarkAsUnread) ||
(showPin && onTogglePin)
const hasEditSection =
(showRename && onRename) ||
(showCreate && onCreate) ||
Expand Down Expand Up @@ -447,6 +457,17 @@ export function ContextMenu({
Mark as unread
</DropdownMenuItem>
)}
{showPin && onTogglePin && (
<DropdownMenuItem
onSelect={() => {
onTogglePin()
onClose()
}}
>
{isPinned ? <PinOff className='size-[14px]' /> : <Pin className='size-[14px]' />}
{isPinned ? 'Unpin' : 'Pin'}
</DropdownMenuItem>
)}
{hasStatusSection && (hasEditSection || hasCopySection) && <DropdownMenuSeparator />}

{showRename && onRename && (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import { memo, useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react'
import { createLogger } from '@sim/logger'
import { Compass, MoreHorizontal } from 'lucide-react'
import { Compass, MoreHorizontal, Pin } from 'lucide-react'
import Image from 'next/image'
import Link from 'next/link'
import { useParams, usePathname, useRouter } from 'next/navigation'
Expand Down Expand Up @@ -91,6 +91,7 @@ import {
useMarkTaskRead,
useMarkTaskUnread,
useRenameTask,
useSetTaskPinned,
useTasks,
} from '@/hooks/queries/tasks'
import { useUpdateWorkflow } from '@/hooks/queries/workflows'
Expand Down Expand Up @@ -144,6 +145,7 @@ const SidebarTaskItem = memo(function SidebarTaskItem({
isSelected,
isActive,
isUnread,
isPinned,
isMenuOpen,
showCollapsedTooltips,
onMultiSelectClick,
Expand All @@ -156,6 +158,7 @@ const SidebarTaskItem = memo(function SidebarTaskItem({
isSelected: boolean
isActive: boolean
isUnread: boolean
isPinned: boolean
isMenuOpen: boolean
showCollapsedTooltips: boolean
onMultiSelectClick: (taskId: string, shiftKey: boolean) => void
Expand Down Expand Up @@ -219,6 +222,9 @@ const SidebarTaskItem = memo(function SidebarTaskItem({
{!isActive && isUnread && !isCurrentRoute && !isMenuOpen && (
<span className='absolute size-[7px] rounded-full bg-[var(--brand-accent)] group-hover:hidden' />
)}
{!isActive && !isUnread && isPinned && !isCurrentRoute && !isMenuOpen && (
<Pin className='absolute size-[12px] text-[var(--text-icon)] group-hover:hidden' />
)}
<button
type='button'
aria-label='Task options'
Expand Down Expand Up @@ -581,6 +587,7 @@ export const Sidebar = memo(function Sidebar() {
const deleteTasksMutation = useDeleteTasks(workspaceId)
const markTaskReadMutation = useMarkTaskRead(workspaceId)
const markTaskUnreadMutation = useMarkTaskUnread(workspaceId)
const setTaskPinnedMutation = useSetTaskPinned(workspaceId)
const renameTaskMutation = useRenameTask(workspaceId)
const tasksHover = useHoverMenu()
const workflowsHover = useHoverMenu()
Expand Down Expand Up @@ -790,6 +797,10 @@ export const Sidebar = memo(function Sidebar() {
: [],
[fetchedTasks, workspaceId]
)
const tasksRef = useRef(tasks)
useEffect(() => {
tasksRef.current = tasks
}, [tasks])

const { data: fetchedTables = [] } = useTablesList(workspaceId)
const { data: fetchedFiles = [] } = useWorkspaceFiles(workspaceId)
Expand Down Expand Up @@ -930,6 +941,15 @@ export const Sidebar = memo(function Sidebar() {
markTaskUnreadMutation.mutate(ids[0])
}, [])

const handleToggleTaskPin = useCallback(() => {
const { taskIds: ids } = contextMenuSelectionRef.current
if (ids.length !== 1) return
const taskId = ids[0]
const task = tasksRef.current.find((t) => t.id === taskId)
if (!task) return
setTaskPinnedMutation.mutate({ chatId: taskId, pinned: !task.isPinned })
}, [])

const handleStartTaskRename = useCallback(() => {
const { taskIds: ids } = contextMenuSelectionRef.current
if (ids.length !== 1) return
Expand Down Expand Up @@ -998,12 +1018,6 @@ export const Sidebar = memo(function Sidebar() {
})
}, [workflowId, workflowsLoading])

useEffect(() => {
if (!isOnWorkflowPage && !isCollapsed) {
setSidebarWidth(SIDEBAR_WIDTH.MIN)
}
}, [isOnWorkflowPage, isCollapsed, setSidebarWidth])

const handleCreateWorkflow = useCallback(async () => {
const workflowId = await createWorkflow()
if (workflowId) {
Expand Down Expand Up @@ -1521,6 +1535,7 @@ export const Sidebar = memo(function Sidebar() {
isSelected={isSelected}
isActive={!!task.isActive}
isUnread={!!task.isUnread}
isPinned={!!task.isPinned}
isMenuOpen={menuOpenTaskId === task.id}
showCollapsedTooltips={showCollapsedTooltips}
onMultiSelectClick={handleTaskClick}
Expand Down Expand Up @@ -1771,6 +1786,7 @@ export const Sidebar = memo(function Sidebar() {
onOpenInNewTab={handleTaskOpenInNewTab}
onMarkAsRead={handleMarkTaskAsRead}
onMarkAsUnread={handleMarkTaskAsUnread}
onTogglePin={handleToggleTaskPin}
onRename={handleStartTaskRename}
onDelete={handleDeleteTask}
showOpenInNewTab={!isMultiTaskContextMenu}
Expand All @@ -1780,6 +1796,8 @@ export const Sidebar = memo(function Sidebar() {
!!activeTaskContextMenuItem &&
!activeTaskContextMenuItem.isUnread
}
showPin={!isMultiTaskContextMenu && !!activeTaskContextMenuItem}
isPinned={!!activeTaskContextMenuItem?.isPinned}
showRename={!isMultiTaskContextMenu}
showDuplicate={false}
showColorChange={false}
Expand All @@ -1800,21 +1818,19 @@ export const Sidebar = memo(function Sidebar() {
</div>
</aside>

{(isCollapsed || isOnWorkflowPage) && (
<div
className={cn(
'absolute top-0 right-0 bottom-0 z-20 w-[8px] translate-x-1/2',
isCollapsed ? 'cursor-e-resize' : 'cursor-ew-resize'
)}
onMouseDown={isCollapsed ? undefined : handleMouseDown}
onClick={isCollapsed ? toggleCollapsed : undefined}
onKeyDown={handleEdgeKeyDown}
role={isCollapsed ? 'button' : 'separator'}
tabIndex={0}
aria-orientation={isCollapsed ? undefined : 'vertical'}
aria-label={isCollapsed ? 'Expand sidebar' : 'Resize sidebar'}
/>
)}
<div
className={cn(
'absolute top-0 right-0 bottom-0 z-20 w-[8px] translate-x-1/2',
isCollapsed ? 'cursor-e-resize' : 'cursor-ew-resize'
)}
onMouseDown={isCollapsed ? undefined : handleMouseDown}
onClick={isCollapsed ? toggleCollapsed : undefined}
onKeyDown={handleEdgeKeyDown}
role={isCollapsed ? 'button' : 'separator'}
tabIndex={0}
aria-orientation={isCollapsed ? undefined : 'vertical'}
aria-label={isCollapsed ? 'Expand sidebar' : 'Resize sidebar'}
/>
</div>

<SearchModal
Expand Down
23 changes: 23 additions & 0 deletions apps/sim/blocks/blocks/mothership.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,25 @@ export const MothershipBlock: BlockConfig<MothershipResponse> = {
type: 'short-input',
placeholder: 'e.g., user-123, session-abc, customer-456',
},
{
id: 'attachmentFiles',
title: 'Attachments',
type: 'file-upload',
canonicalParamId: 'files',
placeholder: 'Upload files to attach',
mode: 'basic',
multiple: true,
required: false,
},
{
id: 'fileReferences',
title: 'Attachments',
type: 'short-input',
canonicalParamId: 'files',
placeholder: 'Reference files from previous blocks',
mode: 'advanced',
required: false,
},
],
tools: {
access: [],
Expand All @@ -54,6 +73,10 @@ export const MothershipBlock: BlockConfig<MothershipResponse> = {
type: 'string',
description: 'Mothership chat ID to continue; generated when omitted',
},
files: {
type: 'file',
description: 'Files to send to Mothership as attachments',
},
},
outputs: {
content: { type: 'string', description: 'Generated response content' },
Expand Down
Loading
Loading