chore: optimize imports and useShallow (#3740)

* chore: fix conflicts

* chore: fix review changes

* chore: fix review changes

* chore: fix review changes
This commit is contained in:
Adithya Krishna
2026-03-24 10:51:09 -07:00
committed by GitHub
parent b09a073c29
commit 7b6149dc23
44 changed files with 353 additions and 79 deletions
@@ -60,7 +60,6 @@ export default async function Page({ params }: { params: Promise<{ slug: string
sizes='(max-width: 768px) 100vw, 450px'
priority
itemProp='image'
unoptimized
/>
</div>
</div>
@@ -144,7 +143,6 @@ export default async function Page({ params }: { params: Promise<{ slug: string
className='h-[160px] w-full object-cover'
sizes='(max-width: 640px) 100vw, (max-width: 1024px) 50vw, 33vw'
loading='lazy'
unoptimized
/>
<div className='p-3'>
<div className='mb-1 text-[#999] text-xs'>
@@ -64,7 +64,6 @@ export default async function AuthorPage({ params }: { params: Promise<{ id: str
width={600}
height={315}
className='h-[160px] w-full object-cover transition-transform group-hover:scale-[1.02]'
unoptimized
/>
<div className='p-3'>
<div className='mb-1 text-[#999] text-xs'>
@@ -32,7 +32,6 @@ export function PostGrid({ posts }: { posts: Post[] }) {
src={p.ogImage}
alt={p.title}
sizes='(max-width: 768px) 100vw, (max-width: 1024px) 50vw, 33vw'
unoptimized
priority={index < 6}
loading={index < 6 ? undefined : 'lazy'}
fill
@@ -3,9 +3,17 @@
import { type RefObject, useCallback, useEffect, useRef, useState } from 'react'
import { createLogger } from '@sim/logger'
import { Mic, MicOff, Phone } from 'lucide-react'
import dynamic from 'next/dynamic'
import { Button } from '@/components/ui/button'
import { cn } from '@/lib/core/utils/cn'
import { ParticlesVisualization } from '@/app/chat/components/voice-interface/components/particles'
const ParticlesVisualization = dynamic(
() =>
import('@/app/chat/components/voice-interface/components/particles').then(
(mod) => mod.ParticlesVisualization
),
{ ssr: false }
)
const logger = createLogger('VoiceInterface')
@@ -1,6 +1,7 @@
import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { createLogger } from '@sim/logger'
import { Star, User } from 'lucide-react'
import Image from 'next/image'
import { useParams, useRouter } from 'next/navigation'
import { VerifiedBadge } from '@/components/ui/verified-badge'
import { cn } from '@/lib/core/utils/cn'
@@ -281,9 +282,14 @@ function TemplateCardInner({
<div className='mt-[10px] flex items-center justify-between'>
<div className='flex min-w-0 items-center gap-[8px]'>
{authorImageUrl ? (
<div className='h-[20px] w-[20px] flex-shrink-0 overflow-hidden rounded-full'>
<img src={authorImageUrl} alt={author} className='h-full w-full object-cover' />
</div>
<Image
src={authorImageUrl}
alt={author}
width={20}
height={20}
className='flex-shrink-0 rounded-full object-cover'
unoptimized
/>
) : (
<div className='flex h-[20px] w-[20px] flex-shrink-0 items-center justify-center rounded-full bg-[var(--surface-7)]'>
<User className='h-[12px] w-[12px] text-[var(--text-muted)]' />
@@ -36,7 +36,7 @@ function WorkflowsListInner({
searchQuery: string
segmentDurationMs: number
}) {
const { workflows } = useWorkflowRegistry()
const workflows = useWorkflowRegistry((s) => s.workflows)
return (
<div className='flex h-full flex-col overflow-hidden rounded-[6px] bg-[var(--surface-2)] dark:bg-[var(--surface-1)]'>
@@ -2,6 +2,7 @@
import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { Loader2 } from 'lucide-react'
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'
@@ -146,7 +147,14 @@ function DashboardInner({ stats, isLoading, error }: DashboardProps) {
const [lastAnchorIndices, setLastAnchorIndices] = useState<Record<string, number>>({})
const lastAnchorIndicesRef = useRef<Record<string, number>>({})
const { workflowIds, searchQuery, toggleWorkflowId, timeRange } = useFilterStore()
const { workflowIds, searchQuery, toggleWorkflowId, timeRange } = useFilterStore(
useShallow((s) => ({
workflowIds: s.workflowIds,
searchQuery: s.searchQuery,
toggleWorkflowId: s.toggleWorkflowId,
timeRange: s.timeRange,
}))
)
const allWorkflows = useWorkflowRegistry((state) => state.workflows)
@@ -3,6 +3,7 @@
import { memo, useCallback, useMemo, useState } from 'react'
import { ArrowUp, Bell, Library, MoreHorizontal, RefreshCw } from 'lucide-react'
import { useParams } from 'next/navigation'
import { useShallow } from 'zustand/react/shallow'
import {
Button,
Combobox,
@@ -195,7 +196,25 @@ export const LogsToolbar = memo(function LogsToolbar({
setDateRange,
clearDateRange,
resetFilters,
} = useFilterStore()
} = useFilterStore(
useShallow((s) => ({
level: s.level,
setLevel: s.setLevel,
workflowIds: s.workflowIds,
setWorkflowIds: s.setWorkflowIds,
folderIds: s.folderIds,
setFolderIds: s.setFolderIds,
triggers: s.triggers,
setTriggers: s.setTriggers,
timeRange: s.timeRange,
setTimeRange: s.setTimeRange,
startDate: s.startDate,
endDate: s.endDate,
setDateRange: s.setDateRange,
clearDateRange: s.clearDateRange,
resetFilters: s.resetFilters,
}))
)
const [datePickerOpen, setDatePickerOpen] = useState(false)
const [previousTimeRange, setPreviousTimeRange] = useState(timeRange)
@@ -3,6 +3,7 @@
import { useCallback, useEffect, useMemo, useReducer, useRef, useState } from 'react'
import { useQueryClient } from '@tanstack/react-query'
import { useParams } from 'next/navigation'
import { useShallow } from 'zustand/react/shallow'
import {
Bell,
Button,
@@ -230,7 +231,30 @@ export default function Logs() {
setTimeRange,
setDateRange,
clearDateRange,
} = useFilterStore()
} = useFilterStore(
useShallow((s) => ({
setWorkspaceId: s.setWorkspaceId,
initializeFromURL: s.initializeFromURL,
timeRange: s.timeRange,
startDate: s.startDate,
endDate: s.endDate,
level: s.level,
workflowIds: s.workflowIds,
folderIds: s.folderIds,
setWorkflowIds: s.setWorkflowIds,
setSearchQuery: s.setSearchQuery,
triggers: s.triggers,
viewMode: s.viewMode,
setViewMode: s.setViewMode,
resetFilters: s.resetFilters,
setLevel: s.setLevel,
setFolderIds: s.setFolderIds,
setTriggers: s.setTriggers,
setTimeRange: s.setTimeRange,
setDateRange: s.setDateRange,
clearDateRange: s.clearDateRange,
}))
)
useEffect(() => {
setWorkspaceId(workspaceId)
@@ -1133,7 +1157,25 @@ function LogsFilterPanel({ searchQuery, onSearchQueryChange }: LogsFilterPanelPr
setDateRange,
clearDateRange,
resetFilters,
} = useFilterStore()
} = useFilterStore(
useShallow((s) => ({
level: s.level,
setLevel: s.setLevel,
workflowIds: s.workflowIds,
setWorkflowIds: s.setWorkflowIds,
folderIds: s.folderIds,
setFolderIds: s.setFolderIds,
triggers: s.triggers,
setTriggers: s.setTriggers,
timeRange: s.timeRange,
setTimeRange: s.setTimeRange,
startDate: s.startDate,
endDate: s.endDate,
setDateRange: s.setDateRange,
clearDateRange: s.clearDateRange,
resetFilters: s.resetFilters,
}))
)
const [datePickerOpen, setDatePickerOpen] = useState(false)
const [previousTimeRange, setPreviousTimeRange] = useState(timeRange)
@@ -1,5 +1,6 @@
import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { Star, User } from 'lucide-react'
import Image from 'next/image'
import { useParams, useRouter } from 'next/navigation'
import { VerifiedBadge } from '@/components/ui/verified-badge'
import { cn } from '@/lib/core/utils/cn'
@@ -288,9 +289,14 @@ function TemplateCardInner({
<div className='mt-[10px] flex items-center justify-between'>
<div className='flex min-w-0 flex-1 items-center gap-[6px]'>
{authorImageUrl ? (
<div className='h-[20px] w-[20px] flex-shrink-0 overflow-hidden rounded-full'>
<img src={authorImageUrl} alt={author} className='h-full w-full object-cover' />
</div>
<Image
src={authorImageUrl}
alt={author}
width={20}
height={20}
className='flex-shrink-0 rounded-full object-cover'
unoptimized
/>
) : (
<div className='flex h-[20px] w-[20px] flex-shrink-0 items-center justify-center rounded-full bg-[var(--surface-7)]'>
<User className='h-[12px] w-[12px] text-[var(--text-tertiary)]' />
@@ -11,6 +11,7 @@ import {
Square,
X,
} from 'lucide-react'
import { useShallow } from 'zustand/react/shallow'
import {
Badge,
Button,
@@ -220,7 +221,7 @@ interface StartInputFormatField {
* position across sessions using the floating chat store.
*/
export function Chat() {
const { activeWorkflowId } = useWorkflowRegistry()
const activeWorkflowId = useWorkflowRegistry((s) => s.activeWorkflowId)
const blocks = useWorkflowStore((state) => state.blocks)
const triggerWorkflowUpdate = useWorkflowStore((state) => state.triggerUpdate)
const setSubBlockValue = useSubBlockStore((state) => state.setValue)
@@ -242,7 +243,26 @@ export function Chat() {
getConversationId,
clearChat,
exportChatCSV,
} = useChatStore()
} = useChatStore(
useShallow((s) => ({
isChatOpen: s.isChatOpen,
chatPosition: s.chatPosition,
chatWidth: s.chatWidth,
chatHeight: s.chatHeight,
setIsChatOpen: s.setIsChatOpen,
setChatPosition: s.setChatPosition,
setChatDimensions: s.setChatDimensions,
messages: s.messages,
addMessage: s.addMessage,
selectedWorkflowOutputs: s.selectedWorkflowOutputs,
setSelectedWorkflowOutput: s.setSelectedWorkflowOutput,
appendMessageContent: s.appendMessageContent,
finalizeMessageStream: s.finalizeMessageStream,
getConversationId: s.getConversationId,
clearChat: s.clearChat,
exportChatCSV: s.exportChatCSV,
}))
)
const hasConsoleHydrated = useTerminalConsoleStore((state) => state._hasHydrated)
const entriesFromStore = useTerminalConsoleStore((state) => state.entries)
@@ -3,6 +3,7 @@
import type React from 'react'
import { useMemo } from 'react'
import { RepeatIcon, SplitIcon } from 'lucide-react'
import { useShallow } from 'zustand/react/shallow'
import { Combobox, type ComboboxOptionGroup } from '@/components/emcn'
import { getEffectiveBlockOutputs } from '@/lib/workflows/blocks/block-outputs'
import { hasTriggerCapability } from '@/lib/workflows/triggers/trigger-utils'
@@ -80,7 +81,14 @@ export function OutputSelect({
maxHeight = 200,
}: OutputSelectProps) {
const blocks = useWorkflowStore((state) => state.blocks)
const { isShowingDiff, isDiffReady, hasActiveDiff, baselineWorkflow } = useWorkflowDiffStore()
const { isShowingDiff, isDiffReady, hasActiveDiff, baselineWorkflow } = useWorkflowDiffStore(
useShallow((s) => ({
isShowingDiff: s.isShowingDiff,
isDiffReady: s.isDiffReady,
hasActiveDiff: s.hasActiveDiff,
baselineWorkflow: s.baselineWorkflow,
}))
)
const subBlockValues = useSubBlockStore((state) =>
workflowId ? state.workflowValues[workflowId] : null
)
@@ -58,7 +58,7 @@ const commands: CommandItem[] = [
export function CommandList() {
const params = useParams()
const router = useRouter()
const { open: openSearchModal } = useSearchModalStore()
const openSearchModal = useSearchModalStore((s) => s.open)
const preventZoomRef = usePreventZoom()
const workspaceId = params.workspaceId as string | undefined
@@ -195,7 +195,6 @@ export function CommandList() {
filter:
'brightness(0) saturate(100%) invert(69%) sepia(0%) saturate(0%) hue-rotate(202deg) brightness(94%) contrast(89%)',
}}
priority
/>
</div>
@@ -85,7 +85,7 @@ export function VariablesInput({
const params = useParams()
const workflowId = params.workflowId as string
const [storeValue, setStoreValue] = useSubBlockValue<VariableAssignment[]>(blockId, subBlockId)
const { variables: workflowVariables } = useVariablesStore()
const workflowVariables = useVariablesStore((s) => s.variables)
const accessiblePrefixes = useAccessibleReferencePrefixes(blockId)
const [showTags, setShowTags] = useState(false)
@@ -1,4 +1,5 @@
import { useCallback, useEffect, useRef, useState } from 'react'
import { useShallow } from 'zustand/react/shallow'
import { useToolbarStore } from '@/stores/panel'
/**
@@ -76,7 +77,12 @@ export function useToolbarResize({
triggersContentRef,
triggersHeaderRef,
}: UseToolbarResizeProps) {
const { toolbarTriggersHeight, setToolbarTriggersHeight } = useToolbarStore()
const { toolbarTriggersHeight, setToolbarTriggersHeight } = useToolbarStore(
useShallow((s) => ({
toolbarTriggersHeight: s.toolbarTriggersHeight,
setToolbarTriggersHeight: s.setToolbarTriggersHeight,
}))
)
const [isResizing, setIsResizing] = useState(false)
const startYRef = useRef<number>(0)
@@ -1,4 +1,5 @@
import { useCallback, useEffect } from 'react'
import { useShallow } from 'zustand/react/shallow'
import { PANEL_WIDTH } from '@/stores/constants'
import { usePanelStore } from '@/stores/panel'
@@ -13,7 +14,13 @@ const CONTENT_WINDOW_GAP = 8
* @returns Resize state and handlers
*/
export function usePanelResize() {
const { setPanelWidth, isResizing, setIsResizing } = usePanelStore()
const { setPanelWidth, isResizing, setIsResizing } = usePanelStore(
useShallow((s) => ({
setPanelWidth: s.setPanelWidth,
isResizing: s.isResizing,
setIsResizing: s.setIsResizing,
}))
)
/**
* Handles mouse down on resize handle
@@ -3,6 +3,7 @@
import { useCallback, useEffect, useMemo, useState } from 'react'
import { Plus, X } from 'lucide-react'
import Editor from 'react-simple-code-editor'
import { useShallow } from 'zustand/react/shallow'
import {
Badge,
Button,
@@ -92,17 +93,33 @@ const STRINGS = {
* - Uses emcn Input/Code/Combobox components for a consistent UI
*/
export function Variables() {
const { activeWorkflowId } = useWorkflowRegistry()
const activeWorkflowId = useWorkflowRegistry((s) => s.activeWorkflowId)
const { isOpen, position, width, height, setIsOpen, setPosition, setDimensions } =
useVariablesStore()
useVariablesStore(
useShallow((s) => ({
isOpen: s.isOpen,
position: s.position,
width: s.width,
height: s.height,
setIsOpen: s.setIsOpen,
setPosition: s.setPosition,
setDimensions: s.setDimensions,
}))
)
const { getVariablesByWorkflowId } = usePanelVariablesStore()
const variables = usePanelVariablesStore((s) => s.variables)
const { collaborativeUpdateVariable, collaborativeAddVariable, collaborativeDeleteVariable } =
useCollaborativeWorkflow()
const workflowVariables = activeWorkflowId ? getVariablesByWorkflowId(activeWorkflowId) : []
const workflowVariables = useMemo(
() =>
activeWorkflowId
? Object.values(variables).filter((v) => v.workflowId === activeWorkflowId)
: [],
[variables, activeWorkflowId]
)
const actualPosition = useMemo(
() => getVariablesPosition(position, width, height),
@@ -4,6 +4,7 @@ import { memo, useCallback, useRef, useState } from 'react'
import { createLogger } from '@sim/logger'
import { Scan } from 'lucide-react'
import { useReactFlow } from 'reactflow'
import { useShallow } from 'zustand/react/shallow'
import {
Button,
ChevronDown,
@@ -36,7 +37,9 @@ const logger = createLogger('WorkflowControls')
export const WorkflowControls = memo(function WorkflowControls() {
const reactFlowInstance = useReactFlow()
const { fitViewToBounds } = useCanvasViewport(reactFlowInstance)
const { mode, setMode } = useCanvasModeStore()
const { mode, setMode } = useCanvasModeStore(
useShallow((s) => ({ mode: s.mode, setMode: s.setMode }))
)
const { undo, redo } = useCollaborativeWorkflow()
const showWorkflowControls = useShowActionBar()
const updateSetting = useUpdateGeneralSetting()
@@ -2,6 +2,7 @@ import { useCallback, useEffect, useRef, useState } from 'react'
import { createLogger } from '@sim/logger'
import { useQueryClient } from '@tanstack/react-query'
import { v4 as uuidv4 } from 'uuid'
import { useShallow } from 'zustand/react/shallow'
import { buildTraceSpans } from '@/lib/logs/execution/trace-spans/trace-spans'
import { processStreamingBlockLogs } from '@/lib/tokenization'
import {
@@ -97,12 +98,27 @@ function normalizeErrorMessage(error: unknown): string {
export function useWorkflowExecution() {
const queryClient = useQueryClient()
const currentWorkflow = useCurrentWorkflow()
const { activeWorkflowId, workflows } = useWorkflowRegistry()
const { activeWorkflowId, workflows } = useWorkflowRegistry(
useShallow((s) => ({ activeWorkflowId: s.activeWorkflowId, workflows: s.workflows }))
)
const { toggleConsole, addConsole, updateConsole, cancelRunningEntries, clearExecutionEntries } =
useTerminalConsoleStore()
useTerminalConsoleStore(
useShallow((s) => ({
toggleConsole: s.toggleConsole,
addConsole: s.addConsole,
updateConsole: s.updateConsole,
cancelRunningEntries: s.cancelRunningEntries,
clearExecutionEntries: s.clearExecutionEntries,
}))
)
const hasHydrated = useTerminalConsoleStore((s) => s._hasHydrated)
const { getAllVariables } = useEnvironmentStore()
const { getVariablesByWorkflowId, variables } = useVariablesStore()
const getAllVariables = useEnvironmentStore((s) => s.getAllVariables)
const { getVariablesByWorkflowId, variables } = useVariablesStore(
useShallow((s) => ({
getVariablesByWorkflowId: s.getVariablesByWorkflowId,
variables: s.variables,
}))
)
const { isExecuting, isDebugging, pendingBlocks, executor, debugContext } =
useCurrentWorkflowExecution()
const setCurrentExecutionId = useExecutionStore((s) => s.setCurrentExecutionId)
@@ -2,6 +2,7 @@
import { memo, useCallback, useEffect, useMemo } from 'react'
import clsx from 'clsx'
import { useShallow } from 'zustand/react/shallow'
import { EmptyAreaContextMenu } from '@/app/workspace/[workspaceId]/w/components/sidebar/components/workflow-list/components/empty-area-context-menu'
import { FolderItem } from '@/app/workspace/[workspaceId]/w/components/sidebar/components/workflow-list/components/folder-item/folder-item'
import { WorkflowItem } from '@/app/workspace/[workspaceId]/w/components/sidebar/components/workflow-list/components/workflow-item/workflow-item'
@@ -78,7 +79,14 @@ export const WorkflowList = memo(function WorkflowList({
}: WorkflowListProps) {
const { isLoading: foldersLoading } = useFolders(workspaceId)
const folders = useFolderStore((state) => state.folders)
const { getFolderTree, expandedFolders, getFolderPath, setExpanded } = useFolderStore()
const { getFolderTree, expandedFolders, getFolderPath, setExpanded } = useFolderStore(
useShallow((s) => ({
getFolderTree: s.getFolderTree,
expandedFolders: s.expandedFolders,
getFolderPath: s.getFolderPath,
setExpanded: s.setExpanded,
}))
)
const {
isOpen: isEmptyAreaMenuOpen,
@@ -47,7 +47,8 @@ export function useDragDrop(options: UseDragDropOptions = {}) {
const workspaceId = params.workspaceId as string | undefined
const reorderWorkflowsMutation = useReorderWorkflows()
const reorderFoldersMutation = useReorderFolders()
const { setExpanded, expandedFolders } = useFolderStore()
const setExpanded = useFolderStore((s) => s.setExpanded)
const expandedFolders = useFolderStore((s) => s.expandedFolders)
const handleAutoScroll = useCallback(() => {
if (!scrollContainerRef.current) {
@@ -1,4 +1,5 @@
import { useCallback } from 'react'
import { useShallow } from 'zustand/react/shallow'
import { useFolderStore } from '@/stores/folders/store'
interface UseFolderSelectionProps {
@@ -44,7 +45,15 @@ export function useFolderSelection({
selectFolderOnly,
selectFolderRange,
toggleFolderSelection,
} = useFolderStore()
} = useFolderStore(
useShallow((s) => ({
selectedFolders: s.selectedFolders,
lastSelectedFolderId: s.lastSelectedFolderId,
selectFolderOnly: s.selectFolderOnly,
selectFolderRange: s.selectFolderRange,
toggleFolderSelection: s.toggleFolderSelection,
}))
)
/**
* Deselect any workflows whose folder (or any ancestor folder) is currently selected.
@@ -1,4 +1,5 @@
import { useCallback, useEffect } from 'react'
import { useShallow } from 'zustand/react/shallow'
import { SIDEBAR_WIDTH } from '@/stores/constants'
import { useSidebarStore } from '@/stores/sidebar/store'
@@ -10,7 +11,13 @@ import { useSidebarStore } from '@/stores/sidebar/store'
* @returns Resize state and handlers
*/
export function useSidebarResize() {
const { setSidebarWidth, isResizing, setIsResizing } = useSidebarStore()
const { setSidebarWidth, isResizing, setIsResizing } = useSidebarStore(
useShallow((s) => ({
setSidebarWidth: s.setSidebarWidth,
isResizing: s.isResizing,
setIsResizing: s.setIsResizing,
}))
)
/**
* Handles mouse down on resize handle
@@ -1,4 +1,5 @@
import { useCallback } from 'react'
import { useShallow } from 'zustand/react/shallow'
import { useFolderStore } from '@/stores/folders/store'
interface UseWorkflowSelectionProps {
@@ -30,7 +31,14 @@ export function useWorkflowSelection({
activeWorkflowId,
workflowAncestorFolderIds,
}: UseWorkflowSelectionProps) {
const { selectedWorkflows, selectOnly, selectRange, toggleWorkflowSelection } = useFolderStore()
const { selectedWorkflows, selectOnly, selectRange, toggleWorkflowSelection } = useFolderStore(
useShallow((s) => ({
selectedWorkflows: s.selectedWorkflows,
selectOnly: s.selectOnly,
selectRange: s.selectRange,
toggleWorkflowSelection: s.toggleWorkflowSelection,
}))
)
/**
* After a workflow selection change, deselect any folder that is an ancestor of a selected
@@ -36,8 +36,8 @@ interface UseCanDeleteReturn {
* @returns Functions to check deletion eligibility
*/
export function useCanDelete({ workspaceId }: UseCanDeleteProps): UseCanDeleteReturn {
const { workflows } = useWorkflowRegistry()
const { folders } = useFolderStore()
const workflows = useWorkflowRegistry((s) => s.workflows)
const folders = useFolderStore((s) => s.folders)
/**
* Pre-computed data structures for efficient lookups
@@ -46,7 +46,8 @@ export function useDeleteSelection({
onSuccess,
}: UseDeleteSelectionProps) {
const router = useRouter()
const { workflows, removeWorkflow } = useWorkflowRegistry()
const workflows = useWorkflowRegistry((s) => s.workflows)
const removeWorkflow = useWorkflowRegistry((s) => s.removeWorkflow)
const deleteFolderMutation = useDeleteFolderMutation()
const [isDeleting, setIsDeleting] = useState(false)
@@ -42,7 +42,8 @@ export function useDeleteWorkflow({
}: UseDeleteWorkflowProps) {
const router = useRouter()
const queryClient = useQueryClient()
const { workflows, removeWorkflow } = useWorkflowRegistry()
const workflows = useWorkflowRegistry((s) => s.workflows)
const removeWorkflow = useWorkflowRegistry((s) => s.removeWorkflow)
const [isDeleting, setIsDeleting] = useState(false)
/**
@@ -89,8 +89,8 @@ function collectSubfolders(
* Hook for managing folder export to ZIP.
*/
export function useExportFolder({ folderId, onSuccess }: UseExportFolderProps) {
const { workflows } = useWorkflowRegistry()
const { folders } = useFolderStore()
const workflows = useWorkflowRegistry((s) => s.workflows)
const folders = useFolderStore((s) => s.folders)
const [isExporting, setIsExporting] = useState(false)
const hasWorkflows = useMemo(() => {
@@ -12,7 +12,8 @@ const logger = createLogger('WorkflowsPage')
export default function WorkflowsPage() {
const router = useRouter()
const { workflows, setActiveWorkflow } = useWorkflowRegistry()
const workflows = useWorkflowRegistry((s) => s.workflows)
const setActiveWorkflow = useWorkflowRegistry((s) => s.setActiveWorkflow)
const params = useParams()
const workspaceId = params.workspaceId as string
const [isMounted, setIsMounted] = useState(false)
@@ -12,7 +12,7 @@ import {
} from 'react'
import { createLogger } from '@sim/logger'
import { useParams } from 'next/navigation'
import { io, type Socket } from 'socket.io-client'
import type { Socket } from 'socket.io-client'
import { getEnv } from '@/lib/core/config/env'
import { useOperationQueueStore } from '@/stores/operation-queue/store'
@@ -197,8 +197,9 @@ export function SocketProvider({ children, user }: SocketProviderProps) {
initializedRef.current = true
setIsConnecting(true)
const initializeSocket = () => {
const initializeSocket = async () => {
try {
const { io } = await import('socket.io-client')
const socketUrl = getEnv('NEXT_PUBLIC_SOCKET_URL') || 'http://localhost:3002'
logger.info('Attempting to connect to Socket.IO server', {
+34 -14
View File
@@ -36,10 +36,13 @@ export interface A2AAgent {
*/
export const a2aAgentKeys = {
all: ['a2a-agents'] as const,
list: (workspaceId: string) => [...a2aAgentKeys.all, 'list', workspaceId] as const,
detail: (agentId: string) => [...a2aAgentKeys.all, 'detail', agentId] as const,
lists: () => [...a2aAgentKeys.all, 'list'] as const,
list: (workspaceId: string) => [...a2aAgentKeys.lists(), workspaceId] as const,
details: () => [...a2aAgentKeys.all, 'detail'] as const,
detail: (agentId: string) => [...a2aAgentKeys.details(), agentId] as const,
byWorkflows: () => [...a2aAgentKeys.all, 'byWorkflow'] as const,
byWorkflow: (workspaceId: string, workflowId: string) =>
[...a2aAgentKeys.all, 'byWorkflow', workspaceId, workflowId] as const,
[...a2aAgentKeys.byWorkflows(), workspaceId, workflowId] as const,
}
/**
@@ -148,9 +151,11 @@ export function useCreateA2AAgent() {
return useMutation({
mutationFn: createA2AAgent,
onSuccess: () => {
// Invalidate all a2a-agent queries (list, detail, byWorkflow, etc.)
queryClient.invalidateQueries({
queryKey: a2aAgentKeys.all,
queryKey: a2aAgentKeys.lists(),
})
queryClient.invalidateQueries({
queryKey: a2aAgentKeys.byWorkflows(),
})
},
})
@@ -197,10 +202,15 @@ export function useUpdateA2AAgent() {
return useMutation({
mutationFn: updateA2AAgent,
onSuccess: () => {
// Invalidate all a2a-agent queries (list, detail, byWorkflow, etc.)
onSuccess: (_data, variables) => {
queryClient.invalidateQueries({
queryKey: a2aAgentKeys.all,
queryKey: a2aAgentKeys.lists(),
})
queryClient.invalidateQueries({
queryKey: a2aAgentKeys.detail(variables.agentId),
})
queryClient.invalidateQueries({
queryKey: a2aAgentKeys.byWorkflows(),
})
},
})
@@ -227,10 +237,15 @@ export function useDeleteA2AAgent() {
return useMutation({
mutationFn: deleteA2AAgent,
onSuccess: () => {
// Invalidate all a2a-agent queries (list, detail, byWorkflow, etc.)
onSuccess: (_data, variables) => {
queryClient.invalidateQueries({
queryKey: a2aAgentKeys.all,
queryKey: a2aAgentKeys.lists(),
})
queryClient.invalidateQueries({
queryKey: a2aAgentKeys.detail(variables.agentId),
})
queryClient.invalidateQueries({
queryKey: a2aAgentKeys.byWorkflows(),
})
},
})
@@ -272,10 +287,15 @@ export function usePublishA2AAgent() {
return useMutation({
mutationFn: publishA2AAgent,
onSuccess: () => {
// Invalidate all a2a-agent queries (list, detail, byWorkflow, etc.)
onSuccess: (_data, variables) => {
queryClient.invalidateQueries({
queryKey: a2aAgentKeys.all,
queryKey: a2aAgentKeys.lists(),
})
queryClient.invalidateQueries({
queryKey: a2aAgentKeys.detail(variables.agentId),
})
queryClient.invalidateQueries({
queryKey: a2aAgentKeys.byWorkflows(),
})
},
})
+6 -1
View File
@@ -1,6 +1,6 @@
'use client'
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
import { keepPreviousData, useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
import { fetchJson } from '@/hooks/selectors/helpers'
export interface CredentialSet {
@@ -88,6 +88,7 @@ export function useCredentialSets(organizationId?: string, enabled = true) {
queryFn: ({ signal }) => fetchCredentialSets(organizationId ?? '', signal),
enabled: Boolean(organizationId) && enabled,
staleTime: 60 * 1000,
placeholderData: keepPreviousData,
})
}
@@ -112,6 +113,7 @@ export function useCredentialSetDetail(id?: string, enabled = true) {
queryFn: ({ signal }) => fetchCredentialSetById(id ?? '', signal),
enabled: Boolean(id) && enabled,
staleTime: 60 * 1000,
placeholderData: keepPreviousData,
})
}
@@ -316,6 +318,9 @@ export function useDeleteCredentialSet() {
queryKey: credentialSetKeys.list(variables.organizationId),
})
queryClient.invalidateQueries({ queryKey: credentialSetKeys.memberships() })
queryClient.invalidateQueries({
queryKey: credentialSetKeys.detail(variables.credentialSetId),
})
},
})
}
+6 -1
View File
@@ -1,7 +1,7 @@
import { useCallback } from 'react'
import { createLogger } from '@sim/logger'
import type { QueryClient } from '@tanstack/react-query'
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
import { keepPreviousData, useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
import type { WorkflowDeploymentVersionResponse } from '@/lib/workflows/persistence/utils'
import { useWorkflowRegistry } from '@/stores/workflows/registry/store'
import type { WorkflowState } from '@/stores/workflows/workflow/types'
@@ -89,6 +89,7 @@ export function useDeploymentInfo(workflowId: string | null, options?: { enabled
queryFn: ({ signal }) => fetchDeploymentInfo(workflowId!, signal),
enabled: Boolean(workflowId) && (options?.enabled ?? true),
staleTime: 30 * 1000, // 30 seconds
placeholderData: keepPreviousData,
})
}
@@ -123,6 +124,7 @@ export function useDeployedWorkflowState(
queryFn: ({ signal }) => fetchDeployedWorkflowState(workflowId!, signal),
enabled: Boolean(workflowId) && (options?.enabled ?? true),
staleTime: 30 * 1000,
placeholderData: keepPreviousData,
})
}
@@ -162,6 +164,7 @@ export function useDeploymentVersions(workflowId: string | null, options?: { ena
queryFn: ({ signal }) => fetchDeploymentVersions(workflowId!, signal),
enabled: Boolean(workflowId) && (options?.enabled ?? true),
staleTime: 30 * 1000, // 30 seconds
placeholderData: keepPreviousData,
})
}
@@ -209,6 +212,7 @@ export function useChatDeploymentStatus(
queryFn: ({ signal }) => fetchChatDeploymentStatus(workflowId!, signal),
enabled: Boolean(workflowId) && (options?.enabled ?? true),
staleTime: 30 * 1000, // 30 seconds
placeholderData: keepPreviousData,
})
}
@@ -256,6 +260,7 @@ export function useChatDetail(chatId: string | null, options?: { enabled?: boole
queryFn: ({ signal }) => fetchChatDetail(chatId!, signal),
enabled: Boolean(chatId) && (options?.enabled ?? true),
staleTime: 30 * 1000, // 30 seconds
placeholderData: keepPreviousData,
})
}
+8 -5
View File
@@ -731,7 +731,7 @@ export function useCreateKnowledgeBase(workspaceId?: string) {
mutationFn: createKnowledgeBase,
onSuccess: () => {
queryClient.invalidateQueries({
queryKey: knowledgeKeys.all,
queryKey: knowledgeKeys.lists(),
})
},
})
@@ -779,10 +779,10 @@ export function useUpdateKnowledgeBase(workspaceId?: string) {
},
onSuccess: (_, { knowledgeBaseId }) => {
queryClient.invalidateQueries({
queryKey: knowledgeKeys.detail(knowledgeBaseId),
queryKey: knowledgeKeys.lists(),
})
queryClient.invalidateQueries({
queryKey: knowledgeKeys.all,
queryKey: knowledgeKeys.detail(knowledgeBaseId),
})
},
})
@@ -815,9 +815,12 @@ export function useDeleteKnowledgeBase(workspaceId?: string) {
return useMutation({
mutationFn: deleteKnowledgeBase,
onSuccess: () => {
onSuccess: (_data, variables) => {
queryClient.invalidateQueries({
queryKey: knowledgeKeys.all,
queryKey: knowledgeKeys.lists(),
})
queryClient.invalidateQueries({
queryKey: knowledgeKeys.detail(variables.knowledgeBaseId),
})
},
})
+2 -1
View File
@@ -1,5 +1,5 @@
import { createLogger } from '@sim/logger'
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
import { keepPreviousData, useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
import type { CoreTriggerType } from '@/stores/logs/filters/types'
const logger = createLogger('NotificationQueries')
@@ -97,6 +97,7 @@ export function useNotifications(workspaceId?: string) {
queryFn: ({ signal }) => fetchNotifications(workspaceId!, signal),
enabled: Boolean(workspaceId),
staleTime: 30 * 1000,
placeholderData: keepPreviousData,
})
}
+6 -1
View File
@@ -1,5 +1,5 @@
import { createLogger } from '@sim/logger'
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
import { keepPreviousData, useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
import { parseCronToHumanReadable } from '@/lib/workflows/schedules/utils'
import { deploymentKeys } from '@/hooks/queries/deployments'
@@ -96,6 +96,7 @@ export function useWorkspaceSchedules(workspaceId?: string) {
},
enabled: Boolean(workspaceId),
staleTime: 30 * 1000,
placeholderData: keepPreviousData,
})
}
@@ -113,6 +114,7 @@ export function useScheduleQuery(
enabled: !!workflowId && !!blockId && (options?.enabled ?? true),
staleTime: 30 * 1000, // 30 seconds
retry: false,
placeholderData: keepPreviousData,
})
}
@@ -234,6 +236,7 @@ export function useDisableSchedule() {
},
onSuccess: ({ workspaceId }) => {
queryClient.invalidateQueries({ queryKey: scheduleKeys.list(workspaceId) })
queryClient.invalidateQueries({ queryKey: scheduleKeys.details() })
},
onError: (error) => {
logger.error('Failed to disable schedule', { error })
@@ -268,6 +271,7 @@ export function useDeleteSchedule() {
},
onSuccess: ({ workspaceId }) => {
queryClient.invalidateQueries({ queryKey: scheduleKeys.list(workspaceId) })
queryClient.invalidateQueries({ queryKey: scheduleKeys.details() })
},
onError: (error) => {
logger.error('Failed to delete schedule', { error })
@@ -311,6 +315,7 @@ export function useUpdateSchedule() {
},
onSuccess: ({ workspaceId }) => {
queryClient.invalidateQueries({ queryKey: scheduleKeys.list(workspaceId) })
queryClient.invalidateQueries({ queryKey: scheduleKeys.details() })
},
onError: (error) => {
logger.error('Failed to update schedule', { error })
+10 -4
View File
@@ -79,7 +79,8 @@ export const taskKeys = {
all: ['tasks'] as const,
lists: () => [...taskKeys.all, 'list'] as const,
list: (workspaceId: string | undefined) => [...taskKeys.lists(), workspaceId ?? ''] as const,
detail: (chatId: string | undefined) => [...taskKeys.all, 'detail', chatId ?? ''] as const,
details: () => [...taskKeys.all, 'detail'] as const,
detail: (chatId: string | undefined) => [...taskKeys.details(), chatId ?? ''] as const,
}
interface TaskResponse {
@@ -177,8 +178,9 @@ export function useDeleteTask(workspaceId?: string) {
const queryClient = useQueryClient()
return useMutation({
mutationFn: deleteTask,
onSettled: () => {
onSettled: (_data, _error, chatId) => {
queryClient.invalidateQueries({ queryKey: taskKeys.list(workspaceId) })
queryClient.removeQueries({ queryKey: taskKeys.detail(chatId) })
},
})
}
@@ -192,8 +194,11 @@ export function useDeleteTasks(workspaceId?: string) {
mutationFn: async (chatIds: string[]) => {
await Promise.all(chatIds.map(deleteTask))
},
onSettled: () => {
onSettled: (_data, _error, chatIds) => {
queryClient.invalidateQueries({ queryKey: taskKeys.list(workspaceId) })
for (const chatId of chatIds) {
queryClient.removeQueries({ queryKey: taskKeys.detail(chatId) })
}
},
})
}
@@ -232,8 +237,9 @@ export function useRenameTask(workspaceId?: string) {
queryClient.setQueryData(taskKeys.list(workspaceId), context.previousTasks)
}
},
onSettled: () => {
onSettled: (_data, _error, variables) => {
queryClient.invalidateQueries({ queryKey: taskKeys.list(workspaceId) })
queryClient.invalidateQueries({ queryKey: taskKeys.detail(variables.chatId) })
},
})
}
+1
View File
@@ -60,6 +60,7 @@ export function useWorkflowState(workflowId: string | undefined) {
queryFn: ({ signal }) => fetchWorkflowState(workflowId!, signal),
enabled: Boolean(workflowId),
staleTime: 30 * 1000, // 30 seconds
placeholderData: keepPreviousData,
})
}
+2 -1
View File
@@ -120,8 +120,9 @@ export function useDeleteWorkspace() {
return response.json()
},
onSuccess: () => {
onSuccess: (_data, variables) => {
queryClient.invalidateQueries({ queryKey: workspaceKeys.lists() })
queryClient.invalidateQueries({ queryKey: workspaceKeys.detail(variables.workspaceId) })
},
})
}
+4 -1
View File
@@ -1,3 +1,4 @@
import { cache } from 'react'
import { sso } from '@better-auth/sso'
import { stripe } from '@better-auth/stripe'
import { db } from '@sim/db'
@@ -3023,7 +3024,7 @@ export const auth = betterAuth({
},
})
export async function getSession() {
async function getSessionImpl() {
if (isAuthDisabled) {
await ensureAnonymousUserExists()
return createAnonymousSession()
@@ -3035,5 +3036,7 @@ export async function getSession() {
})
}
export const getSession = cache(getSessionImpl)
export const signIn = auth.api.signInEmail
export const signUp = auth.api.signUpEmail
-1
View File
@@ -13,7 +13,6 @@ export const mdxComponents: MDXRemoteProps['components'] = {
className={clsx('h-auto w-full rounded-lg', props.className)}
sizes='(max-width: 768px) 100vw, 800px'
loading='lazy'
unoptimized
/>
),
h2: ({ children, className, ...props }: any) => (
+1 -1
View File
@@ -1,5 +1,4 @@
import { createLogger } from '@sim/logger'
import { toPng } from 'html-to-image'
const logger = createLogger('OGCapturePreview')
@@ -30,6 +29,7 @@ export async function captureWorkflowPreview(
try {
logger.info(`Capturing workflow preview for OG image (attempt ${attempt}/${retries})`)
const { toPng } = await import('html-to-image')
const dataUrl = await toPng(element, {
width: OG_IMAGE_WIDTH,
height: OG_IMAGE_HEIGHT,
@@ -1,5 +1,4 @@
import { createLogger } from '@sim/logger'
import JSZip from 'jszip'
import {
type ExportWorkflowState,
sanitizeForExport,
@@ -9,6 +8,11 @@ import type { Variable, WorkflowState } from '@/stores/workflows/workflow/types'
const logger = createLogger('WorkflowImportExport')
async function getJSZip() {
const { default: JSZip } = await import('jszip')
return JSZip
}
export interface WorkflowExportData {
workflow: {
id: string
@@ -138,6 +142,7 @@ export function exportWorkflowToJson(workflowData: WorkflowExportData): string {
* Workflows are placed at the root level (no folder structure).
*/
export async function exportWorkflowsToZip(workflows: WorkflowExportData[]): Promise<Blob> {
const JSZip = await getJSZip()
const zip = new JSZip()
const seenFilenames = new Set<string>()
@@ -182,6 +187,7 @@ export async function exportWorkspaceToZip(
folders: FolderExportData[],
workspaceColor?: string
): Promise<Blob> {
const JSZip = await getJSZip()
const zip = new JSZip()
const foldersMap = new Map(folders.map((f) => [f.id, f]))
@@ -244,6 +250,7 @@ export async function exportFolderToZip(
workflows: WorkflowExportData[],
folders: FolderExportData[]
): Promise<Blob> {
const JSZip = await getJSZip()
const zip = new JSZip()
const foldersMap = new Map(folders.map((f) => [f.id, f]))
@@ -317,6 +324,7 @@ function extractSortOrder(content: string): number | undefined {
export async function extractWorkflowsFromZip(
zipFile: File
): Promise<{ workflows: ImportedWorkflow[]; metadata?: WorkspaceImportMetadata }> {
const JSZip = await getJSZip()
const zip = await JSZip.loadAsync(await zipFile.arrayBuffer())
const workflows: ImportedWorkflow[] = []
let metadata: WorkspaceImportMetadata | undefined
+19
View File
@@ -97,6 +97,25 @@ const nextConfig: NextConfig = {
turbopackSourceMaps: false,
turbopackFileSystemCacheForDev: true,
preloadEntriesOnStart: false,
optimizePackageImports: [
'lucide-react',
'lodash',
'framer-motion',
'reactflow',
'@radix-ui/react-dialog',
'@radix-ui/react-dropdown-menu',
'@radix-ui/react-popover',
'@radix-ui/react-select',
'@radix-ui/react-tabs',
'@radix-ui/react-tooltip',
'@radix-ui/react-accordion',
'@radix-ui/react-checkbox',
'@radix-ui/react-switch',
'@radix-ui/react-slider',
'react-markdown',
'zod',
'date-fns',
],
},
...(isDev && {
allowedDevOrigins: [