11'use client'
22
3- import { memo , useCallback , useEffect , useRef , useState } from 'react'
3+ import { memo , useCallback , useEffect , useMemo , useRef , useState } from 'react'
44import { createLogger } from '@sim/logger'
55import { toError } from '@sim/utils/errors'
6+ import { useQueryClient } from '@tanstack/react-query'
67import { History , Plus , Square } from 'lucide-react'
78import { useParams , useRouter } from 'next/navigation'
89import { usePostHog } from 'posthog-js/react'
@@ -59,6 +60,12 @@ import { useCurrentWorkflow } from '@/app/workspace/[workspaceId]/w/[workflowId]
5960import { useWorkflowExecution } from '@/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-workflow-execution'
6061import { getWorkflowLockToggleIds } from '@/app/workspace/[workspaceId]/w/[workflowId]/utils'
6162import { useDeleteWorkflow , useImportWorkflow } from '@/app/workspace/[workspaceId]/w/hooks'
63+ import { useCopilotChatSelection } from '@/hooks/queries/copilot-chat-selection'
64+ import {
65+ type CopilotChatListItem ,
66+ copilotChatsKeys ,
67+ useCopilotChats ,
68+ } from '@/hooks/queries/copilot-chats'
6269import { useDuplicateWorkflowMutation , useWorkflowMap } from '@/hooks/queries/workflows'
6370import { useCollaborativeWorkflow } from '@/hooks/use-collaborative-workflow'
6471import { usePermissionConfig } from '@/hooks/use-permission-config'
@@ -77,6 +84,7 @@ import { useWorkflowStore } from '@/stores/workflows/workflow/store'
7784import type { WorkflowState } from '@/stores/workflows/workflow/types'
7885
7986const logger = createLogger ( 'Panel' )
87+ const EMPTY_COPILOT_CHATS : readonly CopilotChatListItem [ ] = [ ]
8088/**
8189 * Panel component with resizable width and tab navigation that persists across page refreshes.
8290 *
@@ -222,63 +230,59 @@ export const Panel = memo(function Panel({ workspaceId: propWorkspaceId }: Panel
222230 const currentWorkflow = activeWorkflowId ? workflows [ activeWorkflowId ] : null
223231 const { isSnapshotView } = useCurrentWorkflow ( )
224232
225- const [ copilotChatId , setCopilotChatId ] = useState < string | undefined > ( undefined )
226- const [ copilotChatTitle , setCopilotChatTitle ] = useState < string | null > ( null )
227- const [ copilotChatList , setCopilotChatList ] = useState <
228- { id : string ; title : string | null ; updatedAt : string ; activeStreamId : string | null } [ ]
229- > ( [ ] )
233+ const { chatId : copilotChatId , setChatId : setCopilotChatId } = useCopilotChatSelection (
234+ activeWorkflowId ?? undefined
235+ )
236+
237+ const { data : copilotChatList = EMPTY_COPILOT_CHATS } = useCopilotChats (
238+ activeWorkflowId ?? undefined
239+ )
230240 const [ isCopilotHistoryOpen , setIsCopilotHistoryOpen ] = useState ( false )
231241
232- const copilotChatIdRef = useRef ( copilotChatId )
233- copilotChatIdRef . current = copilotChatId
234- const copilotInitialLoadDoneRef = useRef ( false )
242+ const copilotChatTitle = useMemo (
243+ ( ) =>
244+ copilotChatId ? ( copilotChatList . find ( ( c ) => c . id === copilotChatId ) ?. title ?? null ) : null ,
245+ [ copilotChatId , copilotChatList ]
246+ )
235247
248+ const queryClient = useQueryClient ( )
236249 const loadCopilotChats = useCallback ( ( ) => {
237250 if ( ! activeWorkflowId ) return
238- fetch ( '/api/copilot/chats' )
239- . then ( ( res ) => ( res . ok ? res . json ( ) : { chats : [ ] } ) )
240- . then ( ( data ) => {
241- const allChats = Array . isArray ( data ?. chats ) ? data . chats : [ ]
242- const filtered = allChats . filter (
243- ( c : { workflowId ?: string } ) => c . workflowId === activeWorkflowId
244- ) as Array < {
245- id : string
246- title : string | null
247- updatedAt : string
248- activeStreamId : string | null
249- } >
250- setCopilotChatList ( filtered )
251-
252- const currentId = copilotChatIdRef . current
253- if ( currentId ) {
254- const match = filtered . find ( ( c : { id : string } ) => c . id === currentId )
255- if ( match ?. title ) setCopilotChatTitle ( match . title )
256- }
257-
258- if ( ! copilotInitialLoadDoneRef . current && ! currentId && filtered . length > 0 ) {
259- copilotInitialLoadDoneRef . current = true
260- setCopilotChatId ( filtered [ 0 ] . id )
261- setCopilotChatTitle ( filtered [ 0 ] . title )
262- }
263- copilotInitialLoadDoneRef . current = true
264- } )
265- . catch ( ( ) => { } )
266- } , [ activeWorkflowId ] )
251+ queryClient . invalidateQueries ( { queryKey : copilotChatsKeys . list ( activeWorkflowId ) } )
252+ } , [ activeWorkflowId , queryClient ] )
267253
254+ // Auto-select most recent on first list arrival per workflow, and drop a
255+ // selection that no longer matches anything in the current list (e.g. the
256+ // chat was deleted in another tab).
257+ const autoSelectAttemptedForRef = useRef < Set < string > > ( new Set ( ) )
268258 useEffect ( ( ) => {
269- copilotInitialLoadDoneRef . current = false
270- loadCopilotChats ( )
271- } , [ loadCopilotChats ] )
259+ if ( ! activeWorkflowId ) return
260+
261+ if ( copilotChatId && ! copilotChatList . find ( ( c ) => c . id === copilotChatId ) ) {
262+ setCopilotChatId ( undefined )
263+ return
264+ }
265+
266+ if ( copilotChatId ) return
267+ if ( autoSelectAttemptedForRef . current . has ( activeWorkflowId ) ) return
268+ autoSelectAttemptedForRef . current . add ( activeWorkflowId )
269+
270+ if ( copilotChatList . length > 0 ) {
271+ setCopilotChatId ( copilotChatList [ 0 ] . id )
272+ }
273+ } , [ copilotChatList , copilotChatId , activeWorkflowId , setCopilotChatId ] )
272274
273275 useEffect ( ( ) => {
274276 posthogRef . current = posthog
275277 } , [ posthog ] )
276278
277- const handleCopilotSelectChat = useCallback ( ( chat : { id : string ; title : string | null } ) => {
278- setCopilotChatId ( chat . id )
279- setCopilotChatTitle ( chat . title )
280- setIsCopilotHistoryOpen ( false )
281- } , [ ] )
279+ const handleCopilotSelectChat = useCallback (
280+ ( chat : { id : string ; title : string | null } ) => {
281+ setCopilotChatId ( chat . id )
282+ setIsCopilotHistoryOpen ( false )
283+ } ,
284+ [ setCopilotChatId ]
285+ )
282286
283287 const handleCopilotDeleteChat = useCallback (
284288 ( chatId : string ) => {
@@ -290,13 +294,12 @@ export const Panel = memo(function Panel({ workspaceId: propWorkspaceId }: Panel
290294 . then ( ( ) => {
291295 if ( copilotChatId === chatId ) {
292296 setCopilotChatId ( undefined )
293- setCopilotChatTitle ( null )
294297 }
295298 loadCopilotChats ( )
296299 } )
297300 . catch ( ( ) => { } )
298301 } ,
299- [ copilotChatId , loadCopilotChats ]
302+ [ copilotChatId , loadCopilotChats , setCopilotChatId ]
300303 )
301304
302305 const handleCopilotToolResult = useCallback (
@@ -361,14 +364,13 @@ export const Panel = memo(function Panel({ workspaceId: propWorkspaceId }: Panel
361364 . then ( ( data : { id ?: string } ) => {
362365 if ( data ?. id ) {
363366 setCopilotChatId ( data . id )
364- setCopilotChatTitle ( null )
365367 loadCopilotChats ( )
366368 }
367369 } )
368370 . catch ( ( err ) => {
369371 logger . error ( 'Failed to create copilot chat' , err )
370372 } )
371- } , [ activeWorkflowId , workspaceId , loadCopilotChats ] )
373+ } , [ activeWorkflowId , workspaceId , loadCopilotChats , setCopilotChatId ] )
372374
373375 const prevResolvedRef = useRef < string | undefined > ( undefined )
374376 useEffect ( ( ) => {
@@ -383,7 +385,7 @@ export const Panel = memo(function Panel({ workspaceId: propWorkspaceId }: Panel
383385 } else {
384386 prevResolvedRef . current = copilotResolvedChatId
385387 }
386- } , [ copilotResolvedChatId , copilotChatId , loadCopilotChats ] )
388+ } , [ copilotResolvedChatId , copilotChatId , loadCopilotChats , setCopilotChatId ] )
387389
388390 const wasCopilotSendingRef = useRef ( false )
389391 useEffect ( ( ) => {
0 commit comments