Skip to content

Commit 7fafc00

Browse files
committed
Task management
1 parent fe5ab8a commit 7fafc00

13 files changed

Lines changed: 13218 additions & 40 deletions

File tree

apps/sim/app/api/mothership/chat/route.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ export async function POST(req: NextRequest) {
103103
userId: authenticatedUserId,
104104
workspaceId,
105105
model: 'claude-opus-4-5',
106+
type: 'mothership',
106107
})
107108
currentChat = chatResult.chat
108109
actualChatId = chatResult.chatId || chatId

apps/sim/app/api/mothership/chats/route.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { db } from '@sim/db'
22
import { copilotChats } from '@sim/db/schema'
33
import { createLogger } from '@sim/logger'
4-
import { and, desc, eq, isNull } from 'drizzle-orm'
4+
import { and, desc, eq } from 'drizzle-orm'
55
import { type NextRequest, NextResponse } from 'next/server'
66
import { z } from 'zod'
77
import {
@@ -40,7 +40,7 @@ export async function GET(request: NextRequest) {
4040
and(
4141
eq(copilotChats.userId, userId),
4242
eq(copilotChats.workspaceId, workspaceId),
43-
isNull(copilotChats.workflowId)
43+
eq(copilotChats.type, 'mothership')
4444
)
4545
)
4646
.orderBy(desc(copilotChats.updatedAt))
@@ -75,6 +75,7 @@ export async function POST(request: NextRequest) {
7575
.values({
7676
userId,
7777
workspaceId,
78+
type: 'mothership',
7879
title: null,
7980
model: 'claude-opus-4-5',
8081
messages: [],

apps/sim/app/workspace/[workspaceId]/home/home.tsx

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,18 +16,15 @@ export function Home({ chatId, streamId }: HomeProps = {}) {
1616
const { workspaceId } = useParams<{ workspaceId: string }>()
1717
const router = useRouter()
1818
const [inputValue, setInputValue] = useState('')
19-
const { messages, isSending, sendMessage, stopGeneration, chatBottomRef } = useChat(
20-
workspaceId,
21-
chatId,
22-
streamId
23-
)
19+
const { messages, isSending, currentChatId, sendMessage, stopGeneration, chatBottomRef } =
20+
useChat(workspaceId, chatId, streamId)
2421

2522
const handleSubmit = useCallback(async () => {
2623
const trimmed = inputValue.trim()
2724
if (!trimmed) return
2825
setInputValue('')
2926

30-
if (chatId) {
27+
if (chatId || currentChatId) {
3128
sendMessage(trimmed)
3229
return
3330
}
@@ -54,7 +51,7 @@ export function Home({ chatId, streamId }: HomeProps = {}) {
5451
} catch {
5552
setInputValue(trimmed)
5653
}
57-
}, [inputValue, chatId, sendMessage, workspaceId, router])
54+
}, [inputValue, chatId, currentChatId, sendMessage, workspaceId, router])
5855

5956
const hasMessages = messages.length > 0
6057

apps/sim/app/workspace/[workspaceId]/home/hooks/use-chat.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ export interface UseChatReturn {
2121
messages: ChatMessage[]
2222
isSending: boolean
2323
error: string | null
24+
currentChatId: string | undefined
2425
sendMessage: (message: string) => Promise<void>
2526
stopGeneration: () => void
2627
chatBottomRef: React.RefObject<HTMLDivElement | null>
@@ -384,5 +385,13 @@ export function useChat(
384385
setIsSending(false)
385386
}, [])
386387

387-
return { messages, isSending, error, sendMessage, stopGeneration, chatBottomRef }
388+
return {
389+
messages,
390+
isSending,
391+
error,
392+
currentChatId: chatIdRef.current,
393+
sendMessage,
394+
stopGeneration,
395+
chatBottomRef,
396+
}
388397
}

apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/nav-item-context-menu/nav-item-context-menu.tsx

Lines changed: 13 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -3,43 +3,23 @@
33
import { Popover, PopoverAnchor, PopoverContent, PopoverItem } from '@/components/emcn'
44

55
interface NavItemContextMenuProps {
6-
/**
7-
* Whether the context menu is open
8-
*/
96
isOpen: boolean
10-
/**
11-
* Position of the context menu
12-
*/
137
position: { x: number; y: number }
14-
/**
15-
* Ref for the menu element
16-
*/
178
menuRef: React.RefObject<HTMLDivElement | null>
18-
/**
19-
* Callback when menu should close
20-
*/
219
onClose: () => void
22-
/**
23-
* Callback when open in new tab is clicked
24-
*/
2510
onOpenInNewTab: () => void
26-
/**
27-
* Callback when copy link is clicked
28-
*/
2911
onCopyLink: () => void
12+
onDelete?: () => void
3013
}
3114

32-
/**
33-
* Context menu component for sidebar navigation items.
34-
* Displays navigation-appropriate options (open in new tab, copy link) in a popover at the right-click position.
35-
*/
3615
export function NavItemContextMenu({
3716
isOpen,
3817
position,
3918
menuRef,
4019
onClose,
4120
onOpenInNewTab,
4221
onCopyLink,
22+
onDelete,
4323
}: NavItemContextMenuProps) {
4424
return (
4525
<Popover
@@ -75,6 +55,17 @@ export function NavItemContextMenu({
7555
>
7656
Copy link
7757
</PopoverItem>
58+
{onDelete && (
59+
<PopoverItem
60+
onClick={() => {
61+
onDelete()
62+
onClose()
63+
}}
64+
className='text-[var(--color-error)]'
65+
>
66+
Delete
67+
</PopoverItem>
68+
)}
7869
</PopoverContent>
7970
</Popover>
8071
)

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

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ import {
4646
useImportWorkflow,
4747
useImportWorkspace,
4848
} from '@/app/workspace/[workspaceId]/w/hooks'
49-
import { useTasks } from '@/hooks/queries/tasks'
49+
import { useDeleteTask, useTasks } from '@/hooks/queries/tasks'
5050
import { usePermissionConfig } from '@/hooks/use-permission-config'
5151
import { SIDEBAR_WIDTH } from '@/stores/constants'
5252
import { useFolderStore } from '@/stores/folders/store'
@@ -183,6 +183,7 @@ export const Sidebar = memo(function Sidebar() {
183183
})
184184

185185
const [activeNavItemHref, setActiveNavItemHref] = useState<string | null>(null)
186+
const [activeTaskId, setActiveTaskId] = useState<string | null>(null)
186187
const {
187188
isOpen: isNavContextMenuOpen,
188189
position: navContextMenuPosition,
@@ -191,9 +192,21 @@ export const Sidebar = memo(function Sidebar() {
191192
closeMenu: closeNavContextMenu,
192193
} = useContextMenu()
193194

195+
const deleteTaskMutation = useDeleteTask(workspaceId)
196+
194197
const handleNavItemContextMenu = useCallback(
195198
(e: React.MouseEvent, href: string) => {
196199
setActiveNavItemHref(href)
200+
setActiveTaskId(null)
201+
handleNavContextMenuBase(e)
202+
},
203+
[handleNavContextMenuBase]
204+
)
205+
206+
const handleTaskContextMenu = useCallback(
207+
(e: React.MouseEvent, href: string, taskId: string) => {
208+
setActiveNavItemHref(href)
209+
setActiveTaskId(taskId)
197210
handleNavContextMenuBase(e)
198211
},
199212
[handleNavContextMenuBase]
@@ -202,6 +215,7 @@ export const Sidebar = memo(function Sidebar() {
202215
const handleNavContextMenuClose = useCallback(() => {
203216
closeNavContextMenu()
204217
setActiveNavItemHref(null)
218+
setActiveTaskId(null)
205219
}, [closeNavContextMenu])
206220

207221
const handleNavOpenInNewTab = useCallback(() => {
@@ -221,6 +235,18 @@ export const Sidebar = memo(function Sidebar() {
221235
}
222236
}, [activeNavItemHref])
223237

238+
const handleDeleteTask = useCallback(() => {
239+
if (!activeTaskId) return
240+
const isViewingDeletedTask = pathname === `/workspace/${workspaceId}/task/${activeTaskId}`
241+
deleteTaskMutation.mutate(activeTaskId, {
242+
onSuccess: () => {
243+
if (isViewingDeletedTask) {
244+
router.push(`/workspace/${workspaceId}/home`)
245+
}
246+
},
247+
})
248+
}, [activeTaskId, pathname, workspaceId, deleteTaskMutation, router])
249+
224250
const { handleDuplicateWorkspace: duplicateWorkspace } = useDuplicateWorkspace({
225251
workspaceId,
226252
})
@@ -687,7 +713,7 @@ export const Sidebar = memo(function Sidebar() {
687713
key={task.id}
688714
href={task.href}
689715
className={`mx-[2px] flex h-[28px] items-center gap-[8px] rounded-[8px] px-[8px] text-[14px] hover:bg-[var(--surface-6)] dark:hover:bg-[var(--surface-5)] ${active ? 'bg-[var(--surface-6)] dark:bg-[var(--surface-5)]' : ''}`}
690-
onContextMenu={(e) => handleNavItemContextMenu(e, task.href)}
716+
onContextMenu={(e) => handleTaskContextMenu(e, task.href, task.id)}
691717
>
692718
<Blimp className={`h-[14px] w-[14px] flex-shrink-0 ${textColor}`} />
693719
<div className={`min-w-0 truncate font-medium ${textColor}`}>{task.name}</div>
@@ -813,6 +839,7 @@ export const Sidebar = memo(function Sidebar() {
813839
onClose={handleNavContextMenuClose}
814840
onOpenInNewTab={handleNavOpenInNewTab}
815841
onCopyLink={handleNavCopyLink}
842+
onDelete={activeTaskId ? handleDeleteTask : undefined}
816843
/>
817844
</div>
818845

apps/sim/hooks/queries/tasks.ts

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { keepPreviousData, useQuery } from '@tanstack/react-query'
1+
import { keepPreviousData, useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
22

33
export interface TaskMetadata {
44
id: string
@@ -105,3 +105,27 @@ export function useChatHistory(chatId: string | undefined) {
105105
staleTime: 30 * 1000,
106106
})
107107
}
108+
109+
async function deleteTask(chatId: string): Promise<void> {
110+
const response = await fetch('/api/copilot/chat/delete', {
111+
method: 'DELETE',
112+
headers: { 'Content-Type': 'application/json' },
113+
body: JSON.stringify({ chatId }),
114+
})
115+
if (!response.ok) {
116+
throw new Error('Failed to delete task')
117+
}
118+
}
119+
120+
/**
121+
* Deletes a mothership chat task and invalidates the task list.
122+
*/
123+
export function useDeleteTask(workspaceId?: string) {
124+
const queryClient = useQueryClient()
125+
return useMutation({
126+
mutationFn: deleteTask,
127+
onSuccess: () => {
128+
queryClient.invalidateQueries({ queryKey: taskKeys.list(workspaceId) })
129+
},
130+
})
131+
}

apps/sim/lib/copilot/chat-lifecycle.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,9 @@ export async function resolveOrCreateChat(params: {
2323
workflowId?: string
2424
workspaceId?: string
2525
model: string
26+
type?: 'mothership' | 'copilot'
2627
}): Promise<ChatLoadResult> {
27-
const { chatId, userId, workflowId, workspaceId, model } = params
28+
const { chatId, userId, workflowId, workspaceId, model, type } = params
2829

2930
if (chatId) {
3031
const [chat] = await db
@@ -47,6 +48,7 @@ export async function resolveOrCreateChat(params: {
4748
userId,
4849
...(workflowId ? { workflowId } : {}),
4950
...(workspaceId ? { workspaceId } : {}),
51+
type: type ?? 'copilot',
5052
title: null,
5153
model,
5254
messages: [],
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
DO $$ BEGIN
2+
CREATE TYPE "public"."chat_type" AS ENUM('mothership', 'copilot');
3+
EXCEPTION
4+
WHEN duplicate_object THEN null;
5+
END $$;
6+
--> statement-breakpoint
7+
ALTER TABLE "copilot_chats" ADD COLUMN "type" "public"."chat_type" DEFAULT 'copilot' NOT NULL;
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
CREATE TYPE "public"."chat_type" AS ENUM('mothership', 'copilot');--> statement-breakpoint
2+
ALTER TABLE "copilot_chats" ADD COLUMN "type" "chat_type" DEFAULT 'copilot' NOT NULL;

0 commit comments

Comments
 (0)