diff --git a/apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/components/resource-content/resource-content.tsx b/apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/components/resource-content/resource-content.tsx index 46f78e1f89e..33d4ade4e56 100644 --- a/apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/components/resource-content/resource-content.tsx +++ b/apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/components/resource-content/resource-content.tsx @@ -1,6 +1,6 @@ 'use client' -import { lazy, memo, Suspense, useEffect, useMemo } from 'react' +import { lazy, memo, Suspense, useEffect, useMemo, useRef } from 'react' import { createLogger } from '@sim/logger' import { Square } from 'lucide-react' import { useRouter } from 'next/navigation' @@ -13,6 +13,7 @@ import { SquareArrowUpRight, WorkflowX, } from '@/components/emcn/icons' +import { isApiClientError } from '@/lib/api/client/errors' import type { FilePreviewSession } from '@/lib/copilot/request/session' import { cancelRunToolExecution, @@ -70,6 +71,7 @@ interface ResourceContentProps { previewSession?: FilePreviewSession | null genericResourceData?: GenericResourceData previewContextKey?: string + onNotFound?: (resourceId: string) => void } /** @@ -86,6 +88,7 @@ export const ResourceContent = memo(function ResourceContent({ previewSession, genericResourceData, previewContextKey, + onNotFound, }: ResourceContentProps) { const streamFileName = previewSession?.fileName || 'file.md' const syntheticFile = useMemo(() => { @@ -179,7 +182,13 @@ export const ResourceContent = memo(function ResourceContent({ return case 'log': - return + return ( + onNotFound(resource.id) : undefined} + /> + ) case 'generic': return ( @@ -618,10 +627,20 @@ function EmbeddedFolder({ workspaceId, folderId }: EmbeddedFolderProps) { interface EmbeddedLogProps { logId: string + onNotFound?: () => void } -function EmbeddedLog({ logId }: EmbeddedLogProps) { - const { data: log, isLoading } = useLogDetail(logId) +function EmbeddedLog({ logId, onNotFound }: EmbeddedLogProps) { + const { data: log, isLoading, error } = useLogDetail(logId) + + const onNotFoundRef = useRef(onNotFound) + onNotFoundRef.current = onNotFound + + useEffect(() => { + if (isApiClientError(error) && error.status === 404) { + onNotFoundRef.current?.() + } + }, [error]) if (isLoading) return LOADING_SKELETON diff --git a/apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/mothership-view.tsx b/apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/mothership-view.tsx index fcfb08ff948..4eb7227c850 100644 --- a/apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/mothership-view.tsx +++ b/apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/mothership-view.tsx @@ -128,6 +128,7 @@ export const MothershipView = memo( previewSession={previewForActive} genericResourceData={active.type === 'generic' ? genericResourceData : undefined} previewContextKey={chatId} + onNotFound={(resourceId) => onRemoveResource('log', resourceId)} /> ) : (
diff --git a/apps/sim/app/workspace/[workspaceId]/home/hooks/use-chat.ts b/apps/sim/app/workspace/[workspaceId]/home/hooks/use-chat.ts index b4dccd4d4df..0799193d148 100644 --- a/apps/sim/app/workspace/[workspaceId]/home/hooks/use-chat.ts +++ b/apps/sim/app/workspace/[workspaceId]/home/hooks/use-chat.ts @@ -1420,6 +1420,18 @@ export function useChat( const removeResource = useCallback((resourceType: MothershipResourceType, resourceId: string) => { setResources((prev) => prev.filter((r) => !(r.type === resourceType && r.id === resourceId))) setActiveResourceId((prev) => (prev === resourceId ? null : prev)) + + const persistChatId = chatIdRef.current ?? selectedChatIdRef.current + if (persistChatId) { + // boundary-raw-fetch: fire-and-forget side-effect; intentionally avoids requestJson's response parsing/throw semantics so a transient failure cannot interrupt the caller + fetch('/api/mothership/chat/resources', { + method: 'DELETE', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ chatId: persistChatId, resourceType, resourceId }), + }).catch((err) => { + logger.warn('Failed to persist resource removal', err) + }) + } }, []) const reorderResources = useCallback((newOrder: MothershipResource[]) => { diff --git a/apps/sim/hooks/queries/logs.ts b/apps/sim/hooks/queries/logs.ts index bd5b0e5e695..d2f4bbfa4e9 100644 --- a/apps/sim/hooks/queries/logs.ts +++ b/apps/sim/hooks/queries/logs.ts @@ -7,6 +7,7 @@ import { useQuery, useQueryClient, } from '@tanstack/react-query' +import { isApiClientError } from '@/lib/api/client/errors' import { requestJson } from '@/lib/api/client/request' import { cancelWorkflowExecutionContract, @@ -194,6 +195,8 @@ export function useLogDetail(logId: string | undefined, options?: UseLogDetailOp enabled: Boolean(logId) && (options?.enabled ?? true), refetchInterval: options?.refetchInterval ?? false, staleTime: 30 * 1000, + retry: (failureCount, err) => + !(isApiClientError(err) && err.status === 404) && failureCount < 3, }) } diff --git a/apps/sim/lib/copilot/resources/extraction.ts b/apps/sim/lib/copilot/resources/extraction.ts index 29ca644a21a..6cba5a3bee0 100644 --- a/apps/sim/lib/copilot/resources/extraction.ts +++ b/apps/sim/lib/copilot/resources/extraction.ts @@ -7,7 +7,6 @@ import { FunctionExecute, GenerateImage, GenerateVisualization, - GetWorkflowLogs, Knowledge, KnowledgeBase, UserTable, @@ -30,7 +29,6 @@ const RESOURCE_TOOL_NAMES: Set = new Set([ Knowledge.id, GenerateVisualization.id, GenerateImage.id, - GetWorkflowLogs.id, ]) export function isResourceToolName(toolName: string): boolean { @@ -214,19 +212,6 @@ export function extractResourcesFromToolResult( return resources } - case GetWorkflowLogs.id: { - const entries = Array.isArray(output) ? output : Array.isArray(result.data) ? result.data : [] - const resources: ChatResource[] = [] - for (const entry of entries) { - const rec = asRecord(entry) - const logId = rec.id as string | undefined - if (logId) { - resources.push({ type: 'log', id: logId, title: 'Log' }) - } - } - return resources - } - default: return [] }