Skip to content
Open
39 changes: 25 additions & 14 deletions apps/sim/app/academy/components/sandbox-canvas-provider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,12 @@ import type {
import { validateExercise } from '@/lib/academy/validation'
import { cn } from '@/lib/core/utils/cn'
import { getEffectiveBlockOutputs } from '@/lib/workflows/blocks/block-outputs'
import { getQueryClient } from '@/app/_shell/providers/get-query-client'
import { GlobalCommandsProvider } from '@/app/workspace/[workspaceId]/providers/global-commands-provider'
import { SandboxWorkspacePermissionsProvider } from '@/app/workspace/[workspaceId]/providers/workspace-permissions-provider'
import Workflow from '@/app/workspace/[workspaceId]/w/[workflowId]/workflow'
import { getBlock } from '@/blocks/registry'
import { workflowKeys } from '@/hooks/queries/workflows'
import { SandboxBlockConstraintsContext } from '@/hooks/use-sandbox-block-constraints'
import { useExecutionStore } from '@/stores/execution/store'
import { useTerminalConsoleStore } from '@/stores/terminal/console/store'
Expand Down Expand Up @@ -218,8 +220,13 @@ export function SandboxCanvasProvider({

useWorkflowStore.getState().replaceWorkflowState(workflowState)
useSubBlockStore.getState().initializeFromWorkflow(workflowId, workflowState.blocks)
useWorkflowRegistry.setState((state) => ({
workflows: { ...state.workflows, [workflowId]: syntheticMetadata },

const qc = getQueryClient()
const cacheKey = workflowKeys.list(SANDBOX_WORKSPACE_ID, 'active')
const cached = qc.getQueryData<WorkflowMetadata[]>(cacheKey) ?? []
qc.setQueryData(cacheKey, [...cached.filter((w) => w.id !== workflowId), syntheticMetadata])

useWorkflowRegistry.setState({
activeWorkflowId: workflowId,
hydration: {
phase: 'ready',
Expand All @@ -228,7 +235,7 @@ export function SandboxCanvasProvider({
requestId: null,
error: null,
},
}))
})

logger.info('Sandbox stores hydrated', { workflowId })
setIsReady(true)
Expand Down Expand Up @@ -262,17 +269,21 @@ export function SandboxCanvasProvider({
unsubWorkflow()
unsubSubBlock()
unsubExecution()
useWorkflowRegistry.setState((state) => {
const { [workflowId]: _removed, ...rest } = state.workflows
return {
workflows: rest,
activeWorkflowId: state.activeWorkflowId === workflowId ? null : state.activeWorkflowId,
hydration:
state.hydration.workflowId === workflowId
? { phase: 'idle', workspaceId: null, workflowId: null, requestId: null, error: null }
: state.hydration,
}
})
const cleanupQc = getQueryClient()
const cleanupKey = workflowKeys.list(SANDBOX_WORKSPACE_ID, 'active')
const cleanupCached = cleanupQc.getQueryData<WorkflowMetadata[]>(cleanupKey) ?? []
cleanupQc.setQueryData(
cleanupKey,
cleanupCached.filter((w) => w.id !== workflowId)
)

useWorkflowRegistry.setState((state) => ({
activeWorkflowId: state.activeWorkflowId === workflowId ? null : state.activeWorkflowId,
hydration:
state.hydration.workflowId === workflowId
? { phase: 'idle', workspaceId: null, workflowId: null, requestId: null, error: null }
: state.hydration,
}))
useWorkflowStore.setState({ blocks: {}, edges: [], loops: {}, parallels: {} })
useSubBlockStore.setState((state) => {
const { [workflowId]: _removed, ...rest } = state.workflowValues
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ export function useAvailableResources(
workspaceId: string,
existingKeys: Set<string>
): AvailableItemsByType[] {
const { data: workflows = [] } = useWorkflows(workspaceId, { syncRegistry: false })
const { data: workflows = [] } = useWorkflows(workspaceId)
const { data: tables = [] } = useTablesList(workspaceId)
const { data: files = [] } = useWorkspaceFiles(workspaceId)
const { data: knowledgeBases } = useKnowledgeBasesQuery(workspaceId)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import {
import { Table } from '@/app/workspace/[workspaceId]/tables/[tableId]/components'
import { useUsageLimits } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/hooks'
import { useWorkflowExecution } from '@/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-workflow-execution'
import { useWorkflows } from '@/hooks/queries/workflows'
import { useWorkspaceFiles } from '@/hooks/queries/workspace-files'
import { useSettingsNavigation } from '@/hooks/use-settings-navigation'
import { useExecutionStore } from '@/stores/execution/store'
Expand Down Expand Up @@ -375,15 +376,16 @@ interface EmbeddedWorkflowProps {
}

function EmbeddedWorkflow({ workspaceId, workflowId }: EmbeddedWorkflowProps) {
const workflowExists = useWorkflowRegistry((state) => Boolean(state.workflows[workflowId]))
const isMetadataLoaded = useWorkflowRegistry(
(state) => state.hydration.phase !== 'idle' && state.hydration.phase !== 'metadata-loading'
const { data: workflowList, isPending: isWorkflowsPending } = useWorkflows(workspaceId)
const workflowExists = useMemo(
() => (workflowList ?? []).some((w) => w.id === workflowId),
[workflowList, workflowId]
)
const hasLoadError = useWorkflowRegistry(
(state) => state.hydration.phase === 'error' && state.hydration.workflowId === workflowId
)

if (!isMetadataLoaded) return LOADING_SKELETON
if (isWorkflowsPending) return LOADING_SKELETON

if (!workflowExists || hasLoadError) {
return (
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
'use client'

import type { ElementType, ReactNode } from 'react'
import { type ElementType, type ReactNode, useMemo } from 'react'
import type { QueryClient } from '@tanstack/react-query'
import { useParams } from 'next/navigation'
import {
Database,
File as FileIcon,
Expand All @@ -17,9 +18,8 @@ import type {
} from '@/app/workspace/[workspaceId]/home/types'
import { knowledgeKeys } from '@/hooks/queries/kb/knowledge'
import { tableKeys } from '@/hooks/queries/tables'
import { workflowKeys } from '@/hooks/queries/workflows'
import { useWorkflows, workflowKeys } from '@/hooks/queries/workflows'
import { workspaceFilesKeys } from '@/hooks/queries/workspace-files'
import { useWorkflowRegistry } from '@/stores/workflows/registry/store'

interface DropdownItemRenderProps {
item: { id: string; name: string; [key: string]: unknown }
Expand All @@ -34,7 +34,12 @@ export interface ResourceTypeConfig {
}

function WorkflowTabSquare({ workflowId, className }: { workflowId: string; className?: string }) {
const color = useWorkflowRegistry((state) => state.workflows[workflowId]?.color ?? '#888')
const { workspaceId } = useParams<{ workspaceId: string }>()
const { data: workflowList } = useWorkflows(workspaceId)
const color = useMemo(() => {
const wf = (workflowList ?? []).find((w) => w.id === workflowId)
return wf?.color ?? '#888'
}, [workflowList, workflowId])
return (
<div
className={cn('flex-shrink-0 rounded-[3px] border-[2px]', className)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ const PREVIEW_MODE_LABELS: Record<PreviewMode, string> = {
* tabs always reflect the latest name even after a rename.
*/
function useResourceNameLookup(workspaceId: string): Map<string, string> {
const { data: workflows = [] } = useWorkflows(workspaceId, { syncRegistry: false })
const { data: workflows = [] } = useWorkflows(workspaceId)
const { data: tables = [] } = useTablesList(workspaceId)
const { data: files = [] } = useWorkspaceFiles(workspaceId)
const { data: knowledgeBases } = useKnowledgeBasesQuery(workspaceId)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,8 @@ import {
computeMentionHighlightRanges,
extractContextTokens,
} from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/copilot/components/user-input/utils'
import { getWorkflows } from '@/hooks/queries/workflows'
import type { ChatContext } from '@/stores/panel'
import { useWorkflowRegistry } from '@/stores/workflows/registry/store'

export type { FileAttachmentForApi } from '@/app/workspace/[workspaceId]/home/types'

Expand Down Expand Up @@ -639,7 +639,7 @@ export function UserInput({
case 'workflow':
case 'current_workflow': {
const wfId = (matchingCtx as { workflowId: string }).workflowId
const wfColor = useWorkflowRegistry.getState().workflows[wfId]?.color ?? '#888'
const wfColor = getWorkflows().find((w) => w.id === wfId)?.color ?? '#888'
mentionIconNode = (
<div
className='absolute inset-0 m-auto h-[12px] w-[12px] rounded-[3px] border-[2px]'
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
'use client'

import { useMemo } from 'react'
import { useParams } from 'next/navigation'
import { Database, Table as TableIcon } from '@/components/emcn/icons'
import { getDocumentIcon } from '@/components/icons/document-icons'
import type { ChatMessageContext } from '@/app/workspace/[workspaceId]/home/types'
import { useWorkflowRegistry } from '@/stores/workflows/registry/store'
import { useWorkflows } from '@/hooks/queries/workflows'

const USER_MESSAGE_CLASSES =
'whitespace-pre-wrap break-words [overflow-wrap:anywhere] font-[430] font-[family-name:var(--font-inter)] text-base text-[var(--text-primary)] leading-[23px] tracking-[0] antialiased'
Expand Down Expand Up @@ -44,12 +46,12 @@ function computeMentionRanges(text: string, contexts: ChatMessageContext[]): Men
}

function MentionHighlight({ context }: { context: ChatMessageContext }) {
const workflowColor = useWorkflowRegistry((state) => {
if (context.kind === 'workflow' || context.kind === 'current_workflow') {
return state.workflows[context.workflowId || '']?.color ?? null
}
return null
})
const { workspaceId } = useParams<{ workspaceId: string }>()
const { data: workflowList } = useWorkflows(workspaceId)
const workflowColor = useMemo(() => {
if (context.kind !== 'workflow' && context.kind !== 'current_workflow') return null
return (workflowList ?? []).find((w) => w.id === context.workflowId)?.color ?? null
}, [workflowList, context.kind, context.workflowId])

let icon: React.ReactNode = null
const iconClasses = 'h-[12px] w-[12px] flex-shrink-0 text-[var(--text-icon)]'
Expand Down
44 changes: 23 additions & 21 deletions apps/sim/app/workspace/[workspaceId]/home/hooks/use-chat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
import { VFS_DIR_TO_RESOURCE } from '@/lib/copilot/resource-types'
import { isWorkflowToolName } from '@/lib/copilot/workflow-tools'
import { getNextWorkflowColor } from '@/lib/workflows/colors'
import { getQueryClient } from '@/app/_shell/providers/get-query-client'
import { invalidateResourceQueries } from '@/app/workspace/[workspaceId]/home/components/mothership-view/components/resource-registry'
import { deploymentKeys } from '@/hooks/queries/deployments'
import {
Expand All @@ -35,7 +36,7 @@ import {
useChatHistory,
} from '@/hooks/queries/tasks'
import { getTopInsertionSortOrder } from '@/hooks/queries/utils/top-insertion-sort-order'
import { workflowKeys } from '@/hooks/queries/workflows'
import { getWorkflows, workflowKeys } from '@/hooks/queries/workflows'
import { useExecutionStream } from '@/hooks/use-execution-stream'
import { useExecutionStore } from '@/stores/execution/store'
import { useFolderStore } from '@/stores/folders/store'
Expand Down Expand Up @@ -301,31 +302,32 @@ function getPayloadData(payload: SSEPayload): SSEPayloadData | undefined {
return typeof payload.data === 'object' ? payload.data : undefined
}

/** Adds a workflow to the registry with a top-insertion sort order if it doesn't already exist. */
/** Adds a workflow to the React Query cache with a top-insertion sort order if it doesn't already exist. */
function ensureWorkflowInRegistry(resourceId: string, title: string, workspaceId: string): boolean {
const registry = useWorkflowRegistry.getState()
if (registry.workflows[resourceId]) return false
const workflows = getWorkflows(workspaceId)
if (workflows.find((w) => w.id === resourceId)) return false
const sortOrder = getTopInsertionSortOrder(
registry.workflows,
Object.fromEntries(workflows.map((w) => [w.id, w])),
useFolderStore.getState().folders,
workspaceId,
null
)
useWorkflowRegistry.setState((state) => ({
workflows: {
...state.workflows,
[resourceId]: {
id: resourceId,
name: title,
lastModified: new Date(),
createdAt: new Date(),
color: getNextWorkflowColor(),
workspaceId,
folderId: null,
sortOrder,
},
},
}))
const newMetadata: import('@/stores/workflows/registry/types').WorkflowMetadata = {
id: resourceId,
name: title,
lastModified: new Date(),
createdAt: new Date(),
color: getNextWorkflowColor(),
workspaceId,
folderId: null,
sortOrder,
}
const queryClient = getQueryClient()
const key = workflowKeys.list(workspaceId, 'active')
const current =
queryClient.getQueryData<import('@/stores/workflows/registry/types').WorkflowMetadata[]>(key) ??
[]
queryClient.setQueryData(key, [...current, newMetadata])
return true
}

Expand Down Expand Up @@ -1253,7 +1255,7 @@ export function useChat(
? ((args as Record<string, unknown>).workflowId as string)
: useWorkflowRegistry.getState().activeWorkflowId
if (targetWorkflowId) {
const meta = useWorkflowRegistry.getState().workflows[targetWorkflowId]
const meta = getWorkflows().find((w) => w.id === targetWorkflowId)
const wasAdded = addResource({
type: 'workflow',
id: targetWorkflowId,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { memo } from 'react'
import { useParams } from 'next/navigation'
import { cn } from '@/lib/core/utils/cn'
import {
DELETED_WORKFLOW_COLOR,
DELETED_WORKFLOW_LABEL,
} from '@/app/workspace/[workspaceId]/logs/utils'
import { useWorkflowRegistry } from '@/stores/workflows/registry/store'
import { useWorkflowMap } from '@/hooks/queries/workflows'
import { StatusBar, type StatusBarSegment } from '..'

export interface WorkflowExecutionItem {
Expand Down Expand Up @@ -36,7 +37,8 @@ function WorkflowsListInner({
searchQuery: string
segmentDurationMs: number
}) {
const workflows = useWorkflowRegistry((s) => s.workflows)
const { workspaceId } = useParams<{ workspaceId: string }>()
const { data: workflows = {} } = useWorkflowMap(workspaceId)

return (
<div className='flex h-full flex-col overflow-hidden rounded-md bg-[var(--surface-2)] dark:bg-[var(--surface-1)]'>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@

import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { Loader2 } from 'lucide-react'
import { useParams } from 'next/navigation'
import { useShallow } from 'zustand/react/shallow'
import { Skeleton } from '@/components/emcn'
import { formatLatency } from '@/app/workspace/[workspaceId]/logs/utils'
import type { DashboardStatsResponse, WorkflowStats } from '@/hooks/queries/logs'
import { useWorkflows } from '@/hooks/queries/workflows'
import { useFilterStore } from '@/stores/logs/filters/store'
import { useWorkflowRegistry } from '@/stores/workflows/registry/store'
import { LineChart, WorkflowsList } from './components'

interface WorkflowExecution {
Expand Down Expand Up @@ -156,7 +157,8 @@ function DashboardInner({ stats, isLoading, error }: DashboardProps) {
}))
)

const allWorkflows = useWorkflowRegistry((state) => state.workflows)
const { workspaceId } = useParams<{ workspaceId: string }>()
const { data: allWorkflowList = [], isPending: isWorkflowsPending } = useWorkflows(workspaceId)

const expandedWorkflowId = workflowIds.length === 1 ? workflowIds[0] : null

Expand Down Expand Up @@ -459,7 +461,7 @@ function DashboardInner({ stats, isLoading, error }: DashboardProps) {
)
}

if (Object.keys(allWorkflows).length === 0) {
if (!isWorkflowsPending && allWorkflowList.length === 0) {
return (
<div className='mt-6 flex flex-1 items-center justify-center'>
<div className='text-center text-[var(--text-secondary)]'>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,7 @@ export function WorkflowSelector({
onChange,
error,
}: WorkflowSelectorProps) {
const { data: workflows = [], isPending: isLoading } = useWorkflows(workspaceId, {
syncRegistry: false,
})
const { data: workflows = [], isPending: isLoading } = useWorkflows(workspaceId)

const options: ComboboxOption[] = useMemo(() => {
return workflows.map((w) => ({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import { useEffect, useMemo, useRef, useState } from 'react'
import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu'
import { Search, X } from 'lucide-react'
import { useParams } from 'next/navigation'
import { Badge } from '@/components/emcn'
import { cn } from '@/lib/core/utils/cn'
import { getTriggerOptions } from '@/lib/logs/get-trigger-options'
Expand All @@ -14,8 +15,8 @@ import {
type WorkflowData,
} from '@/lib/logs/search-suggestions'
import { useSearchState } from '@/app/workspace/[workspaceId]/logs/hooks/use-search-state'
import { useWorkflows } from '@/hooks/queries/workflows'
import { useFolderStore } from '@/stores/folders/store'
import { useWorkflowRegistry } from '@/stores/workflows/registry/store'

function truncateFilterValue(field: string, value: string): string {
if ((field === 'executionId' || field === 'workflowId') && value.length > 12) {
Expand All @@ -42,16 +43,17 @@ export function AutocompleteSearch({
className,
onOpenChange,
}: AutocompleteSearchProps) {
const workflows = useWorkflowRegistry((state) => state.workflows)
const { workspaceId } = useParams<{ workspaceId: string }>()
const { data: workflowList = [] } = useWorkflows(workspaceId)
const folders = useFolderStore((state) => state.folders)

const workflowsData = useMemo<WorkflowData[]>(() => {
return Object.values(workflows).map((w) => ({
return workflowList.map((w) => ({
id: w.id,
name: w.name,
description: w.description,
}))
}, [workflows])
}, [workflowList])

const foldersData = useMemo<FolderData[]>(() => {
return Object.values(folders).map((f) => ({
Expand Down
Loading
Loading