mirror of
https://github.com/simstudioai/sim.git
synced 2026-09-24 15:45:35 +08:00
refactor(ui): eliminate prop drilling in editor, home, sidebar, and logs dashboard (#4950)
* refactor(ui): eliminate prop drilling in editor, home, sidebar, and logs dashboard - panel editor: activeSearchTarget was relayed untouched through SubBlock and 30+ input components (depth up to 6); now provided once via ActiveSearchTargetProvider and re-provided at tool-input's synthetic sub-block transformation points; removed the vestigial workspaceId option from SubBlockInputController/useSubBlockInput - home: ChatSurfaceProvider carries chatId/userId and stable interaction callbacks to UserInput/MessageContent/MessageActions; MothershipResourcesProvider carries the five resource operations to ResourceTabs, removing MothershipView's pure-relay props - sidebar: extended SidebarDragContext into SidebarListContext so WorkflowItem/FolderItem read selection/drag callbacks directly; moved the hidden import input up to sidebar.tsx (also fixes import no-op while the list shows a skeleton) - logs dashboard: DashboardSegmentsContext feeds StatusBar directly; WorkflowsList no longer relays segment selection state * refactor(home): sync ChatSurfaceProvider callback refs in a layout effect Mutating refs during render is unsound under concurrent rendering (render may run multiple times before commit); useLayoutEffect commits the latest callbacks before any user event can fire.
This commit is contained in:
@@ -17,6 +17,7 @@ import {
|
||||
toast,
|
||||
} from '@/components/emcn'
|
||||
import { cn } from '@/lib/core/utils/cn'
|
||||
import { useChatSurface } from '@/app/workspace/[workspaceId]/home/components/chat-surface-context'
|
||||
import { useSubmitCopilotFeedback } from '@/hooks/queries/copilot-feedback'
|
||||
import { useForkMothershipChat } from '@/hooks/queries/mothership-chats'
|
||||
import { useFolderStore } from '@/stores/folders/store'
|
||||
@@ -49,7 +50,6 @@ const BUTTON_CLASS =
|
||||
|
||||
interface MessageActionsProps {
|
||||
content: string
|
||||
chatId?: string
|
||||
userQuery?: string
|
||||
requestId?: string
|
||||
messageId?: string
|
||||
@@ -57,13 +57,13 @@ interface MessageActionsProps {
|
||||
|
||||
export const MessageActions = memo(function MessageActions({
|
||||
content,
|
||||
chatId,
|
||||
userQuery,
|
||||
requestId,
|
||||
messageId,
|
||||
}: MessageActionsProps) {
|
||||
const router = useRouter()
|
||||
const params = useParams<{ workspaceId: string }>()
|
||||
const { chatId } = useChatSurface()
|
||||
const [copied, setCopied] = useState(false)
|
||||
const [copiedRequestId, setCopiedRequestId] = useState(false)
|
||||
const [pendingFeedback, setPendingFeedback] = useState<'up' | 'down' | null>(null)
|
||||
|
||||
+106
@@ -0,0 +1,106 @@
|
||||
'use client'
|
||||
|
||||
import {
|
||||
createContext,
|
||||
type ReactNode,
|
||||
useCallback,
|
||||
useContext,
|
||||
useLayoutEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
} from 'react'
|
||||
import type { MothershipResource } from '@/app/workspace/[workspaceId]/home/types'
|
||||
import type { ChatContext } from '@/stores/panel'
|
||||
|
||||
/**
|
||||
* Identity and interaction callbacks shared across a Mothership chat surface
|
||||
* (home conversation view, home initial view, copilot panel). Carried via
|
||||
* context so leaf components (UserInput, MessageContent, MessageActions) can
|
||||
* consume them without relaying through every intermediate component.
|
||||
*/
|
||||
interface ChatSurfaceContextValue {
|
||||
/** Resolved id of the chat backing this surface, if one exists yet. */
|
||||
chatId?: string
|
||||
/** Id of the user interacting with this surface. */
|
||||
userId?: string
|
||||
/** Notifies the surface owner that a context chip was added to the input. */
|
||||
onContextAdd: (context: ChatContext) => void
|
||||
/** Notifies the surface owner that a context chip was removed from the input. */
|
||||
onContextRemove: (context: ChatContext) => void
|
||||
/** Opens a workspace resource referenced from rendered message content. */
|
||||
onWorkspaceResourceSelect: (resource: MothershipResource) => void
|
||||
}
|
||||
|
||||
const noop = () => {}
|
||||
|
||||
const ChatSurfaceContext = createContext<ChatSurfaceContextValue>({
|
||||
onContextAdd: noop,
|
||||
onContextRemove: noop,
|
||||
onWorkspaceResourceSelect: noop,
|
||||
})
|
||||
|
||||
interface ChatSurfaceProviderProps {
|
||||
chatId?: string
|
||||
userId?: string
|
||||
onContextAdd?: (context: ChatContext) => void
|
||||
onContextRemove?: (context: ChatContext) => void
|
||||
onWorkspaceResourceSelect?: (resource: MothershipResource) => void
|
||||
children: ReactNode
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides the chat-surface identity and interaction callbacks to descendants.
|
||||
* Callbacks are latched in refs and exposed as stable wrappers so the memoized
|
||||
* context value only changes when `chatId` or `userId` change — consumers do
|
||||
* not re-render when a parent re-creates a handler.
|
||||
*/
|
||||
export function ChatSurfaceProvider({
|
||||
chatId,
|
||||
userId,
|
||||
onContextAdd,
|
||||
onContextRemove,
|
||||
onWorkspaceResourceSelect,
|
||||
children,
|
||||
}: ChatSurfaceProviderProps) {
|
||||
const onContextAddRef = useRef(onContextAdd)
|
||||
const onContextRemoveRef = useRef(onContextRemove)
|
||||
const onWorkspaceResourceSelectRef = useRef(onWorkspaceResourceSelect)
|
||||
|
||||
useLayoutEffect(() => {
|
||||
onContextAddRef.current = onContextAdd
|
||||
onContextRemoveRef.current = onContextRemove
|
||||
onWorkspaceResourceSelectRef.current = onWorkspaceResourceSelect
|
||||
})
|
||||
|
||||
const stableOnContextAdd = useCallback((context: ChatContext) => {
|
||||
onContextAddRef.current?.(context)
|
||||
}, [])
|
||||
const stableOnContextRemove = useCallback((context: ChatContext) => {
|
||||
onContextRemoveRef.current?.(context)
|
||||
}, [])
|
||||
const stableOnWorkspaceResourceSelect = useCallback((resource: MothershipResource) => {
|
||||
onWorkspaceResourceSelectRef.current?.(resource)
|
||||
}, [])
|
||||
|
||||
const value = useMemo<ChatSurfaceContextValue>(
|
||||
() => ({
|
||||
chatId,
|
||||
userId,
|
||||
onContextAdd: stableOnContextAdd,
|
||||
onContextRemove: stableOnContextRemove,
|
||||
onWorkspaceResourceSelect: stableOnWorkspaceResourceSelect,
|
||||
}),
|
||||
[chatId, userId, stableOnContextAdd, stableOnContextRemove, stableOnWorkspaceResourceSelect]
|
||||
)
|
||||
|
||||
return <ChatSurfaceContext.Provider value={value}>{children}</ChatSurfaceContext.Provider>
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the surrounding chat surface. Outside a provider this returns no-op
|
||||
* callbacks and undefined identity, matching the previous optional-prop
|
||||
* behavior.
|
||||
*/
|
||||
export function useChatSurface(): ChatSurfaceContextValue {
|
||||
return useContext(ChatSurfaceContext)
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export { ChatSurfaceProvider, useChatSurface } from './chat-surface-context'
|
||||
@@ -1,4 +1,5 @@
|
||||
export { ChatMessageAttachments } from './chat-message-attachments'
|
||||
export { ChatSurfaceProvider, useChatSurface } from './chat-surface-context'
|
||||
export { ContextMentionIcon } from './context-mention-icon'
|
||||
export { CreditsChip } from './credits-chip'
|
||||
export {
|
||||
@@ -6,6 +7,10 @@ export {
|
||||
MessageContent,
|
||||
} from './message-content'
|
||||
export { MothershipChat } from './mothership-chat'
|
||||
export {
|
||||
MothershipResourcesProvider,
|
||||
useMothershipResources,
|
||||
} from './mothership-resources-context'
|
||||
export { MothershipView } from './mothership-view'
|
||||
export { QueuedMessages } from './queued-messages'
|
||||
export { SuggestedActions } from './suggested-actions'
|
||||
|
||||
+3
-3
@@ -6,7 +6,8 @@ import { Read as ReadTool, WorkspaceFile } from '@/lib/copilot/generated/tool-ca
|
||||
import { isToolHiddenInUi } from '@/lib/copilot/tools/client/hidden-tools'
|
||||
import { resolveToolDisplay } from '@/lib/copilot/tools/client/store-utils'
|
||||
import { ClientToolCallState } from '@/lib/copilot/tools/client/tool-call-state'
|
||||
import type { ContentBlock, MothershipResource, OptionItem, ToolCallData } from '../../types'
|
||||
import { useChatSurface } from '@/app/workspace/[workspaceId]/home/components/chat-surface-context'
|
||||
import type { ContentBlock, OptionItem, ToolCallData } from '../../types'
|
||||
import { SUBAGENT_LABELS, TOOL_UI_METADATA } from '../../types'
|
||||
import type { AgentGroupItem } from './components'
|
||||
import {
|
||||
@@ -676,7 +677,6 @@ interface MessageContentProps {
|
||||
fallbackContent: string
|
||||
isStreaming: boolean
|
||||
onOptionSelect?: (id: string) => void
|
||||
onWorkspaceResourceSelect?: (resource: MothershipResource) => void
|
||||
}
|
||||
|
||||
function MessageContentInner({
|
||||
@@ -684,8 +684,8 @@ function MessageContentInner({
|
||||
fallbackContent,
|
||||
isStreaming = false,
|
||||
onOptionSelect,
|
||||
onWorkspaceResourceSelect,
|
||||
}: MessageContentProps) {
|
||||
const { onWorkspaceResourceSelect } = useChatSurface()
|
||||
const parsed = useMemo(() => (blocks.length > 0 ? parseBlocks(blocks) : []), [blocks])
|
||||
|
||||
const segments: MessageSegment[] =
|
||||
|
||||
+68
-75
@@ -4,6 +4,7 @@ import { memo, useCallback, useEffect, useLayoutEffect, useMemo, useRef } from '
|
||||
import { cn } from '@/lib/core/utils/cn'
|
||||
import { MessageActions } from '@/app/workspace/[workspaceId]/components'
|
||||
import { ChatMessageAttachments } from '@/app/workspace/[workspaceId]/home/components/chat-message-attachments'
|
||||
import { ChatSurfaceProvider } from '@/app/workspace/[workspaceId]/home/components/chat-surface-context'
|
||||
import {
|
||||
assistantMessageHasRenderableContent,
|
||||
MessageContent,
|
||||
@@ -124,20 +125,16 @@ interface AssistantMessageRowProps {
|
||||
message: ChatMessage
|
||||
isStreaming: boolean
|
||||
precedingUserContent?: string
|
||||
chatId?: string
|
||||
rowClassName: string
|
||||
onOptionSelect?: (id: string) => void
|
||||
onWorkspaceResourceSelect?: (resource: MothershipResource) => void
|
||||
}
|
||||
|
||||
const AssistantMessageRow = memo(function AssistantMessageRow({
|
||||
message,
|
||||
isStreaming,
|
||||
precedingUserContent,
|
||||
chatId,
|
||||
rowClassName,
|
||||
onOptionSelect,
|
||||
onWorkspaceResourceSelect,
|
||||
}: AssistantMessageRowProps) {
|
||||
const blocks = message.contentBlocks ?? EMPTY_BLOCKS
|
||||
const hasAnyBlocks = blocks.length > 0
|
||||
@@ -161,13 +158,11 @@ const AssistantMessageRow = memo(function AssistantMessageRow({
|
||||
fallbackContent={message.content}
|
||||
isStreaming={isStreaming}
|
||||
onOptionSelect={onOptionSelect}
|
||||
onWorkspaceResourceSelect={onWorkspaceResourceSelect}
|
||||
/>
|
||||
{showActions && (
|
||||
<div className='mt-2.5'>
|
||||
<MessageActions
|
||||
content={message.content}
|
||||
chatId={chatId}
|
||||
userQuery={precedingUserContent}
|
||||
requestId={message.requestId}
|
||||
messageId={message.id}
|
||||
@@ -241,17 +236,12 @@ export function MothershipChat({
|
||||
const userInputRef = useRef<UserInputHandle>(null)
|
||||
|
||||
const onSubmitRef = useRef(onSubmit)
|
||||
const onWorkspaceResourceSelectRef = useRef(onWorkspaceResourceSelect)
|
||||
useEffect(() => {
|
||||
onSubmitRef.current = onSubmit
|
||||
onWorkspaceResourceSelectRef.current = onWorkspaceResourceSelect
|
||||
}, [onSubmit, onWorkspaceResourceSelect])
|
||||
}, [onSubmit])
|
||||
const stableOnOptionSelect = useCallback((id: string) => {
|
||||
onSubmitRef.current(id)
|
||||
}, [])
|
||||
const stableOnWorkspaceResourceSelect = useCallback((resource: MothershipResource) => {
|
||||
onWorkspaceResourceSelectRef.current?.(resource)
|
||||
}, [])
|
||||
|
||||
function handleSendQueuedHead() {
|
||||
const topMessage = messageQueue[0]
|
||||
@@ -286,75 +276,78 @@ export function MothershipChat({
|
||||
}, [isStaging, stagedMessageCount, initialScrollBlocked, scrollToBottom])
|
||||
|
||||
return (
|
||||
<div className={cn('flex h-full min-h-0 flex-col', className)}>
|
||||
<div ref={scrollContainerRef} className={styles.scrollContainer}>
|
||||
{isLoading && !hasMessages ? (
|
||||
<MothershipChatSkeleton layout={layout} />
|
||||
) : (
|
||||
<div className={styles.content}>
|
||||
{stagedMessages.map((msg, localIndex) => {
|
||||
const index = stagedOffset + localIndex
|
||||
if (msg.role === 'user') {
|
||||
<ChatSurfaceProvider
|
||||
chatId={chatId}
|
||||
userId={userId}
|
||||
onContextAdd={onContextAdd}
|
||||
onContextRemove={onContextRemove}
|
||||
onWorkspaceResourceSelect={onWorkspaceResourceSelect}
|
||||
>
|
||||
<div className={cn('flex h-full min-h-0 flex-col', className)}>
|
||||
<div ref={scrollContainerRef} className={styles.scrollContainer}>
|
||||
{isLoading && !hasMessages ? (
|
||||
<MothershipChatSkeleton layout={layout} />
|
||||
) : (
|
||||
<div className={styles.content}>
|
||||
{stagedMessages.map((msg, localIndex) => {
|
||||
const index = stagedOffset + localIndex
|
||||
if (msg.role === 'user') {
|
||||
return (
|
||||
<UserMessageRow
|
||||
key={msg.id}
|
||||
content={msg.content}
|
||||
contexts={msg.contexts}
|
||||
attachments={msg.attachments}
|
||||
rowClassName={styles.userRow}
|
||||
bubbleClassName={styles.userBubble}
|
||||
attachmentWidthClassName={styles.attachmentWidth}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
const isLast = index === messages.length - 1
|
||||
return (
|
||||
<UserMessageRow
|
||||
key={msg.id}
|
||||
content={msg.content}
|
||||
contexts={msg.contexts}
|
||||
attachments={msg.attachments}
|
||||
rowClassName={styles.userRow}
|
||||
bubbleClassName={styles.userBubble}
|
||||
attachmentWidthClassName={styles.attachmentWidth}
|
||||
<AssistantMessageRow
|
||||
key={assistantTurnKeyByIndex[index] ?? msg.id}
|
||||
message={msg}
|
||||
isStreaming={isStreamActive && isLast}
|
||||
precedingUserContent={precedingUserContentByIndex[index]}
|
||||
rowClassName={styles.assistantRow}
|
||||
onOptionSelect={isLast ? stableOnOptionSelect : undefined}
|
||||
/>
|
||||
)
|
||||
}
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
const isLast = index === messages.length - 1
|
||||
return (
|
||||
<AssistantMessageRow
|
||||
key={assistantTurnKeyByIndex[index] ?? msg.id}
|
||||
message={msg}
|
||||
isStreaming={isStreamActive && isLast}
|
||||
precedingUserContent={precedingUserContentByIndex[index]}
|
||||
chatId={chatId}
|
||||
rowClassName={styles.assistantRow}
|
||||
onOptionSelect={isLast ? stableOnOptionSelect : undefined}
|
||||
onWorkspaceResourceSelect={stableOnWorkspaceResourceSelect}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
<div
|
||||
className={cn(styles.footer, animateInput && 'animate-slide-in-bottom')}
|
||||
onAnimationEnd={animateInput ? onInputAnimationEnd : undefined}
|
||||
>
|
||||
<div className={styles.footerInner}>
|
||||
<QueuedMessages
|
||||
messageQueue={messageQueue}
|
||||
editingQueuedId={editingQueuedId}
|
||||
dispatchingHeadId={dispatchingHeadId}
|
||||
onRemove={onRemoveQueuedMessage}
|
||||
onSendNow={onSendQueuedMessage}
|
||||
onEdit={handleEditQueued}
|
||||
onCancelEdit={onCancelQueueEdit}
|
||||
/>
|
||||
<UserInput
|
||||
ref={userInputRef}
|
||||
onSubmit={onSubmit}
|
||||
isSending={isStreamActive}
|
||||
onStopGeneration={onStopGeneration}
|
||||
isInitialView={false}
|
||||
onSendQueuedHead={handleSendQueuedHead}
|
||||
onEditQueuedTail={handleEditQueuedTail}
|
||||
draftScopeKey={draftScopeKey}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div
|
||||
className={cn(styles.footer, animateInput && 'animate-slide-in-bottom')}
|
||||
onAnimationEnd={animateInput ? onInputAnimationEnd : undefined}
|
||||
>
|
||||
<div className={styles.footerInner}>
|
||||
<QueuedMessages
|
||||
messageQueue={messageQueue}
|
||||
editingQueuedId={editingQueuedId}
|
||||
dispatchingHeadId={dispatchingHeadId}
|
||||
onRemove={onRemoveQueuedMessage}
|
||||
onSendNow={onSendQueuedMessage}
|
||||
onEdit={handleEditQueued}
|
||||
onCancelEdit={onCancelQueueEdit}
|
||||
/>
|
||||
<UserInput
|
||||
ref={userInputRef}
|
||||
onSubmit={onSubmit}
|
||||
isSending={isStreamActive}
|
||||
onStopGeneration={onStopGeneration}
|
||||
isInitialView={false}
|
||||
userId={userId}
|
||||
onContextAdd={onContextAdd}
|
||||
onContextRemove={onContextRemove}
|
||||
onSendQueuedHead={handleSendQueuedHead}
|
||||
onEditQueuedTail={handleEditQueuedTail}
|
||||
draftScopeKey={draftScopeKey}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ChatSurfaceProvider>
|
||||
)
|
||||
}
|
||||
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
export {
|
||||
MothershipResourcesProvider,
|
||||
useMothershipResources,
|
||||
} from './mothership-resources-context'
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
'use client'
|
||||
|
||||
import { createContext, type ReactNode, useContext, useMemo } from 'react'
|
||||
import type {
|
||||
MothershipResource,
|
||||
MothershipResourceType,
|
||||
} from '@/app/workspace/[workspaceId]/home/types'
|
||||
|
||||
/**
|
||||
* Resource-management operations for the Mothership resource panel. Provided
|
||||
* by the home surface (which owns the resource state via `useChat`) and
|
||||
* consumed at the leaves (`ResourceTabs`, `MothershipView`) so the operations
|
||||
* do not have to be relayed through intermediate components.
|
||||
*/
|
||||
interface MothershipResourcesContextValue {
|
||||
/** Makes the given resource the active tab. */
|
||||
selectResource: (id: string) => void
|
||||
/** Adds a resource to the panel and activates it. */
|
||||
addResource: (resource: MothershipResource) => void
|
||||
/** Removes a resource from the panel. */
|
||||
removeResource: (resourceType: MothershipResourceType, resourceId: string) => void
|
||||
/** Replaces the resource list with a new ordering. */
|
||||
reorderResources: (resources: MothershipResource[]) => void
|
||||
/** Collapses the resource panel. */
|
||||
collapseResource: () => void
|
||||
}
|
||||
|
||||
const MothershipResourcesContext = createContext<MothershipResourcesContextValue | null>(null)
|
||||
|
||||
interface MothershipResourcesProviderProps extends MothershipResourcesContextValue {
|
||||
children: ReactNode
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides resource-management operations to the resource panel subtree. All
|
||||
* operations are expected to be referentially stable; the context value is
|
||||
* memoized on their identities.
|
||||
*/
|
||||
export function MothershipResourcesProvider({
|
||||
selectResource,
|
||||
addResource,
|
||||
removeResource,
|
||||
reorderResources,
|
||||
collapseResource,
|
||||
children,
|
||||
}: MothershipResourcesProviderProps) {
|
||||
const value = useMemo<MothershipResourcesContextValue>(
|
||||
() => ({ selectResource, addResource, removeResource, reorderResources, collapseResource }),
|
||||
[selectResource, addResource, removeResource, reorderResources, collapseResource]
|
||||
)
|
||||
|
||||
return (
|
||||
<MothershipResourcesContext.Provider value={value}>
|
||||
{children}
|
||||
</MothershipResourcesContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the resource-management operations for the surrounding resource panel.
|
||||
* Must be called under a {@link MothershipResourcesProvider}.
|
||||
*/
|
||||
export function useMothershipResources(): MothershipResourcesContextValue {
|
||||
const value = useContext(MothershipResourcesContext)
|
||||
if (!value) {
|
||||
throw new Error('useMothershipResources must be used within a MothershipResourcesProvider')
|
||||
}
|
||||
return value
|
||||
}
|
||||
+15
-17
@@ -16,6 +16,7 @@ import { SIM_RESOURCE_DRAG_TYPE, SIM_RESOURCES_DRAG_TYPE } from '@/lib/copilot/r
|
||||
import { isEphemeralResource } from '@/lib/copilot/resources/types'
|
||||
import { cn } from '@/lib/core/utils/cn'
|
||||
import type { PreviewMode } from '@/app/workspace/[workspaceId]/files/components/file-viewer'
|
||||
import { useMothershipResources } from '@/app/workspace/[workspaceId]/home/components/mothership-resources-context'
|
||||
import { AddResourceDropdown } from '@/app/workspace/[workspaceId]/home/components/mothership-view/components/add-resource-dropdown'
|
||||
import { getResourceConfig } from '@/app/workspace/[workspaceId]/home/components/mothership-view/components/resource-registry'
|
||||
import {
|
||||
@@ -241,11 +242,6 @@ interface ResourceTabsProps {
|
||||
chatId?: string
|
||||
resources: MothershipResource[]
|
||||
activeId: string | null
|
||||
onSelect: (id: string) => void
|
||||
onAddResource: (resource: MothershipResource) => void
|
||||
onRemoveResource: (resourceType: MothershipResourceType, resourceId: string) => void
|
||||
onReorderResources: (resources: MothershipResource[]) => void
|
||||
onCollapse: () => void
|
||||
previewMode?: PreviewMode
|
||||
onCyclePreviewMode?: () => void
|
||||
actions?: ReactNode
|
||||
@@ -256,17 +252,19 @@ export function ResourceTabs({
|
||||
chatId,
|
||||
resources,
|
||||
activeId,
|
||||
onSelect,
|
||||
onAddResource,
|
||||
onRemoveResource,
|
||||
onReorderResources,
|
||||
onCollapse,
|
||||
previewMode,
|
||||
onCyclePreviewMode,
|
||||
actions,
|
||||
}: ResourceTabsProps) {
|
||||
const PreviewModeIcon = PREVIEW_MODE_ICONS[previewMode ?? 'split']
|
||||
const nameLookup = useResourceNameLookup(workspaceId)
|
||||
const {
|
||||
selectResource,
|
||||
addResource: onAddResource,
|
||||
removeResource: onRemoveResource,
|
||||
reorderResources: onReorderResources,
|
||||
collapseResource,
|
||||
} = useMothershipResources()
|
||||
const scrollNodeRef = useRef<HTMLDivElement>(null)
|
||||
|
||||
useEffect(() => {
|
||||
@@ -354,7 +352,7 @@ export function ResourceTabs({
|
||||
const next = new Set<string>()
|
||||
for (let i = start; i <= end; i++) next.add(resources[i].id)
|
||||
setSelectedIds(next)
|
||||
onSelect(resource.id)
|
||||
selectResource(resource.id)
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -370,11 +368,11 @@ export function ResourceTabs({
|
||||
if (activeId === resource.id) {
|
||||
const fallback =
|
||||
findNearestId(resources, idx, next) ?? findNearestId(resources, idx, null)
|
||||
if (fallback) onSelect(fallback)
|
||||
if (fallback) selectResource(fallback)
|
||||
}
|
||||
} else {
|
||||
setSelectedIds((prev) => new Set(prev).add(resource.id))
|
||||
onSelect(resource.id)
|
||||
selectResource(resource.id)
|
||||
}
|
||||
if (!anchorIdRef.current) anchorIdRef.current = resource.id
|
||||
return
|
||||
@@ -383,9 +381,9 @@ export function ResourceTabs({
|
||||
// Plain click: single-select
|
||||
anchorIdRef.current = resource.id
|
||||
setSelectedIds(new Set([resource.id]))
|
||||
onSelect(resource.id)
|
||||
selectResource(resource.id)
|
||||
},
|
||||
[resources, onSelect, selectedIds, activeId]
|
||||
[resources, selectResource, selectedIds, activeId]
|
||||
)
|
||||
|
||||
const handleRemove = useCallback(
|
||||
@@ -553,7 +551,7 @@ export function ResourceTabs({
|
||||
<Tooltip.Trigger asChild>
|
||||
<Button
|
||||
variant='subtle'
|
||||
onClick={onCollapse}
|
||||
onClick={collapseResource}
|
||||
className={RESOURCE_TAB_ICON_BUTTON_CLASS}
|
||||
aria-label='Collapse resource view'
|
||||
>
|
||||
@@ -623,7 +621,7 @@ export function ResourceTabs({
|
||||
workspaceId={workspaceId}
|
||||
existingKeys={existingKeys}
|
||||
onAdd={handleAdd}
|
||||
onSwitch={onSelect}
|
||||
onSwitch={selectResource}
|
||||
excludeTypes={ADD_RESOURCE_EXCLUDED_TYPES}
|
||||
/>
|
||||
)}
|
||||
|
||||
+3
-17
@@ -6,11 +6,11 @@ import { cn } from '@/lib/core/utils/cn'
|
||||
import { getFileExtension } from '@/lib/uploads/utils/file-utils'
|
||||
import type { PreviewMode } from '@/app/workspace/[workspaceId]/files/components/file-viewer'
|
||||
import { RICH_PREVIEWABLE_EXTENSIONS } from '@/app/workspace/[workspaceId]/files/components/file-viewer'
|
||||
import { useMothershipResources } from '@/app/workspace/[workspaceId]/home/components/mothership-resources-context'
|
||||
import { hasRenderableFilePreviewContent } from '@/app/workspace/[workspaceId]/home/hooks/preview'
|
||||
import type {
|
||||
GenericResourceData,
|
||||
MothershipResource,
|
||||
MothershipResourceType,
|
||||
} from '@/app/workspace/[workspaceId]/home/types'
|
||||
import { useUserPermissionsContext } from '@/app/workspace/[workspaceId]/providers/workspace-permissions-provider'
|
||||
import { ResourceActions, ResourceContent, ResourceTabs } from './components'
|
||||
@@ -44,11 +44,6 @@ interface MothershipViewProps {
|
||||
chatId?: string
|
||||
resources: MothershipResource[]
|
||||
activeResourceId: string | null
|
||||
onSelectResource: (id: string) => void
|
||||
onAddResource: (resource: MothershipResource) => void
|
||||
onRemoveResource: (resourceType: MothershipResourceType, resourceId: string) => void
|
||||
onReorderResources: (resources: MothershipResource[]) => void
|
||||
onCollapse: () => void
|
||||
isCollapsed: boolean
|
||||
className?: string
|
||||
previewSession?: FilePreviewSession | null
|
||||
@@ -62,11 +57,6 @@ export const MothershipView = memo(
|
||||
chatId,
|
||||
resources,
|
||||
activeResourceId,
|
||||
onSelectResource,
|
||||
onAddResource,
|
||||
onRemoveResource,
|
||||
onReorderResources,
|
||||
onCollapse,
|
||||
isCollapsed,
|
||||
className,
|
||||
previewSession,
|
||||
@@ -76,6 +66,7 @@ export const MothershipView = memo(
|
||||
) {
|
||||
const active = resources.find((r) => r.id === activeResourceId) ?? resources[0] ?? null
|
||||
const { canEdit } = useUserPermissionsContext()
|
||||
const { removeResource } = useMothershipResources()
|
||||
|
||||
const previewForActive =
|
||||
previewSession && active && shouldShowStreamingFilePanel(previewSession, active)
|
||||
@@ -111,11 +102,6 @@ export const MothershipView = memo(
|
||||
chatId={chatId}
|
||||
resources={resources}
|
||||
activeId={active?.id ?? null}
|
||||
onSelect={onSelectResource}
|
||||
onAddResource={onAddResource}
|
||||
onRemoveResource={onRemoveResource}
|
||||
onReorderResources={onReorderResources}
|
||||
onCollapse={onCollapse}
|
||||
actions={
|
||||
active ? <ResourceActions workspaceId={workspaceId} resource={active} /> : null
|
||||
}
|
||||
@@ -131,7 +117,7 @@ export const MothershipView = memo(
|
||||
previewSession={previewForActive}
|
||||
genericResourceData={active.type === 'generic' ? genericResourceData : undefined}
|
||||
previewContextKey={chatId}
|
||||
onNotFound={(resourceId) => onRemoveResource('log', resourceId)}
|
||||
onNotFound={(resourceId) => removeResource('log', resourceId)}
|
||||
/>
|
||||
) : (
|
||||
<div className='flex h-full items-center justify-center text-[var(--text-muted)] text-sm'>
|
||||
|
||||
@@ -18,6 +18,7 @@ import { getMothershipAttachmentPreviewUrl } from '@/lib/copilot/chat/attachment
|
||||
import { SIM_RESOURCE_DRAG_TYPE, SIM_RESOURCES_DRAG_TYPE } from '@/lib/copilot/resource-types'
|
||||
import { cn } from '@/lib/core/utils/cn'
|
||||
import { CHAT_ACCEPT_ATTRIBUTE } from '@/lib/uploads/utils/validation'
|
||||
import { useChatSurface } from '@/app/workspace/[workspaceId]/home/components/chat-surface-context'
|
||||
import { ContextMentionIcon } from '@/app/workspace/[workspaceId]/home/components/context-mention-icon'
|
||||
import { useAvailableResources } from '@/app/workspace/[workspaceId]/home/components/mothership-view/components/add-resource-dropdown'
|
||||
import { snapSelectionToChips } from '@/app/workspace/[workspaceId]/home/components/user-input/chip-selection'
|
||||
@@ -130,9 +131,6 @@ interface UserInputProps {
|
||||
isSending: boolean
|
||||
onStopGeneration: () => void
|
||||
isInitialView?: boolean
|
||||
userId?: string
|
||||
onContextAdd?: (context: ChatContext) => void
|
||||
onContextRemove?: (context: ChatContext) => void
|
||||
onSendQueuedHead?: () => void
|
||||
onEditQueuedTail?: () => void
|
||||
}
|
||||
@@ -155,9 +153,6 @@ export const UserInput = forwardRef<UserInputHandle, UserInputProps>(function Us
|
||||
isSending,
|
||||
onStopGeneration,
|
||||
isInitialView = true,
|
||||
userId,
|
||||
onContextAdd,
|
||||
onContextRemove,
|
||||
onSendQueuedHead,
|
||||
onEditQueuedTail,
|
||||
},
|
||||
@@ -166,6 +161,7 @@ export const UserInput = forwardRef<UserInputHandle, UserInputProps>(function Us
|
||||
const { workspaceId } = useParams<{ workspaceId: string }>()
|
||||
const { navigateToSettings } = useSettingsNavigation()
|
||||
const { data: skills = [] } = useSkills(workspaceId)
|
||||
const { userId, onContextAdd, onContextRemove } = useChatSurface()
|
||||
const [value, setValue] = useState(() => {
|
||||
if (defaultValue) return defaultValue
|
||||
if (!draftScopeKey) return ''
|
||||
|
||||
@@ -35,8 +35,10 @@ import { useWorkspaceFiles } from '@/hooks/queries/workspace-files'
|
||||
import { useOAuthReturnRouter } from '@/hooks/use-oauth-return'
|
||||
import type { ChatContext } from '@/stores/panel'
|
||||
import {
|
||||
ChatSurfaceProvider,
|
||||
CreditsChip,
|
||||
MothershipChat,
|
||||
MothershipResourcesProvider,
|
||||
MothershipView,
|
||||
SuggestedActions,
|
||||
UserInput,
|
||||
@@ -378,17 +380,20 @@ export function Home({ chatId, userName, userId, initialResourceId = null }: Hom
|
||||
What should we get done{firstName ? `, ${firstName}` : ''}?
|
||||
</h1>
|
||||
<div ref={initialViewInputRef} className='relative w-full max-w-[48rem]'>
|
||||
<UserInput
|
||||
ref={initialViewUserInputRef}
|
||||
defaultValue={initialPrompt}
|
||||
draftScopeKey={draftScopeKey}
|
||||
onSubmit={handleSubmit}
|
||||
isSending={isSending}
|
||||
onStopGeneration={handleStopGeneration}
|
||||
<ChatSurfaceProvider
|
||||
userId={userId}
|
||||
onContextAdd={handleContextAdd}
|
||||
onContextRemove={handleInitialContextRemove}
|
||||
/>
|
||||
>
|
||||
<UserInput
|
||||
ref={initialViewUserInputRef}
|
||||
defaultValue={initialPrompt}
|
||||
draftScopeKey={draftScopeKey}
|
||||
onSubmit={handleSubmit}
|
||||
isSending={isSending}
|
||||
onStopGeneration={handleStopGeneration}
|
||||
/>
|
||||
</ChatSurfaceProvider>
|
||||
{/* Anchored out of flow so expanding/collapsing never shifts the centered input */}
|
||||
<div className='absolute inset-x-0 top-full'>
|
||||
<SuggestedActions
|
||||
@@ -442,22 +447,25 @@ export function Home({ chatId, userName, userId, initialResourceId = null }: Hom
|
||||
</div>
|
||||
)}
|
||||
|
||||
<MothershipView
|
||||
ref={mothershipRef}
|
||||
workspaceId={workspaceId}
|
||||
chatId={resolvedChatId}
|
||||
resources={resources}
|
||||
activeResourceId={activeResourceId}
|
||||
onSelectResource={setActiveResourceId}
|
||||
onAddResource={addResource}
|
||||
onRemoveResource={removeResource}
|
||||
onReorderResources={reorderResources}
|
||||
onCollapse={collapseResource}
|
||||
isCollapsed={isResourceCollapsed}
|
||||
previewSession={previewSession}
|
||||
genericResourceData={genericResourceData ?? undefined}
|
||||
className={skipResourceTransition ? '!transition-none' : undefined}
|
||||
/>
|
||||
<MothershipResourcesProvider
|
||||
selectResource={setActiveResourceId}
|
||||
addResource={addResource}
|
||||
removeResource={removeResource}
|
||||
reorderResources={reorderResources}
|
||||
collapseResource={collapseResource}
|
||||
>
|
||||
<MothershipView
|
||||
ref={mothershipRef}
|
||||
workspaceId={workspaceId}
|
||||
chatId={resolvedChatId}
|
||||
resources={resources}
|
||||
activeResourceId={activeResourceId}
|
||||
isCollapsed={isResourceCollapsed}
|
||||
previewSession={previewSession}
|
||||
genericResourceData={genericResourceData ?? undefined}
|
||||
className={skipResourceTransition ? '!transition-none' : undefined}
|
||||
/>
|
||||
</MothershipResourcesProvider>
|
||||
|
||||
{isResourceCollapsed && (
|
||||
<div className='absolute top-[8.5px] right-[16px]'>
|
||||
|
||||
+49
-19
@@ -1,5 +1,9 @@
|
||||
import { memo, useMemo, useState } from 'react'
|
||||
import { handleKeyboardActivation } from '@/lib/core/utils/keyboard'
|
||||
import {
|
||||
type SegmentSelectionMode,
|
||||
useDashboardSegments,
|
||||
} from '@/app/workspace/[workspaceId]/logs/components/dashboard/dashboard-segments-context'
|
||||
|
||||
export interface StatusBarSegment {
|
||||
successRate: number
|
||||
@@ -9,6 +13,20 @@ export interface StatusBarSegment {
|
||||
timestamp: string
|
||||
}
|
||||
|
||||
interface StatusBarInnerProps {
|
||||
segments: StatusBarSegment[]
|
||||
selectedSegmentIndices: number[] | null
|
||||
onSegmentClick: (
|
||||
workflowId: string,
|
||||
index: number,
|
||||
timestamp: string,
|
||||
mode: SegmentSelectionMode
|
||||
) => void
|
||||
workflowId: string
|
||||
segmentDurationMs: number
|
||||
preferBelow?: boolean
|
||||
}
|
||||
|
||||
function StatusBarInner({
|
||||
segments,
|
||||
selectedSegmentIndices,
|
||||
@@ -16,19 +34,7 @@ function StatusBarInner({
|
||||
workflowId,
|
||||
segmentDurationMs,
|
||||
preferBelow = false,
|
||||
}: {
|
||||
segments: StatusBarSegment[]
|
||||
selectedSegmentIndices: number[] | null
|
||||
onSegmentClick: (
|
||||
workflowId: string,
|
||||
index: number,
|
||||
timestamp: string,
|
||||
mode: 'single' | 'toggle' | 'range'
|
||||
) => void
|
||||
workflowId: string
|
||||
segmentDurationMs: number
|
||||
preferBelow?: boolean
|
||||
}) {
|
||||
}: StatusBarInnerProps) {
|
||||
const [hoverIndex, setHoverIndex] = useState<number | null>(null)
|
||||
|
||||
const labels = useMemo(() => {
|
||||
@@ -137,17 +143,15 @@ function StatusBarInner({
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom equality function for StatusBar memo.
|
||||
* Custom equality function for the memoized StatusBar body.
|
||||
* Performs structural comparison of segments array to avoid re-renders
|
||||
* when poll data returns new object references with identical content.
|
||||
*/
|
||||
function areStatusBarPropsEqual(
|
||||
prev: Parameters<typeof StatusBarInner>[0],
|
||||
next: Parameters<typeof StatusBarInner>[0]
|
||||
): boolean {
|
||||
function areStatusBarPropsEqual(prev: StatusBarInnerProps, next: StatusBarInnerProps): boolean {
|
||||
if (prev.workflowId !== next.workflowId) return false
|
||||
if (prev.segmentDurationMs !== next.segmentDurationMs) return false
|
||||
if (prev.preferBelow !== next.preferBelow) return false
|
||||
if (prev.onSegmentClick !== next.onSegmentClick) return false
|
||||
|
||||
if (prev.selectedSegmentIndices !== next.selectedSegmentIndices) {
|
||||
if (!prev.selectedSegmentIndices || !next.selectedSegmentIndices) return false
|
||||
@@ -177,4 +181,30 @@ function areStatusBarPropsEqual(
|
||||
return true
|
||||
}
|
||||
|
||||
export const StatusBar = memo(StatusBarInner, areStatusBarPropsEqual)
|
||||
const MemoizedStatusBar = memo(StatusBarInner, areStatusBarPropsEqual)
|
||||
|
||||
export interface StatusBarProps {
|
||||
segments: StatusBarSegment[]
|
||||
workflowId: string
|
||||
preferBelow?: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* Status bar for a single workflow row. Reads segment selection state from
|
||||
* DashboardSegmentsContext and delegates to a structurally-memoized body so
|
||||
* only bars whose selection actually changed re-render.
|
||||
*/
|
||||
export function StatusBar({ segments, workflowId, preferBelow = false }: StatusBarProps) {
|
||||
const { selectedSegments, onSegmentClick, segmentDurationMs } = useDashboardSegments()
|
||||
|
||||
return (
|
||||
<MemoizedStatusBar
|
||||
segments={segments}
|
||||
selectedSegmentIndices={selectedSegments[workflowId] || null}
|
||||
onSegmentClick={onSegmentClick}
|
||||
workflowId={workflowId}
|
||||
segmentDurationMs={segmentDurationMs}
|
||||
preferBelow={preferBelow}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
+8
-20
@@ -13,28 +13,19 @@ export interface WorkflowExecutionItem {
|
||||
overallSuccessRate: number
|
||||
}
|
||||
|
||||
interface WorkflowsListProps {
|
||||
filteredExecutions: WorkflowExecutionItem[]
|
||||
expandedWorkflowId: string | null
|
||||
onToggleWorkflow: (workflowId: string) => void
|
||||
searchQuery: string
|
||||
}
|
||||
|
||||
function WorkflowsListInner({
|
||||
filteredExecutions,
|
||||
expandedWorkflowId,
|
||||
onToggleWorkflow,
|
||||
selectedSegments,
|
||||
onSegmentClick,
|
||||
searchQuery,
|
||||
segmentDurationMs,
|
||||
}: {
|
||||
filteredExecutions: WorkflowExecutionItem[]
|
||||
expandedWorkflowId: string | null
|
||||
onToggleWorkflow: (workflowId: string) => void
|
||||
selectedSegments: Record<string, number[]>
|
||||
onSegmentClick: (
|
||||
workflowId: string,
|
||||
segmentIndex: number,
|
||||
timestamp: string,
|
||||
mode: 'single' | 'toggle' | 'range'
|
||||
) => void
|
||||
searchQuery: string
|
||||
segmentDurationMs: number
|
||||
}) {
|
||||
}: WorkflowsListProps) {
|
||||
return (
|
||||
<div className='flex h-full flex-col overflow-hidden rounded-md bg-[var(--surface-2)] dark:bg-[var(--surface-1)]'>
|
||||
{/* Table header */}
|
||||
@@ -99,10 +90,7 @@ function WorkflowsListInner({
|
||||
<div className='flex-1'>
|
||||
<StatusBar
|
||||
segments={workflow.segments}
|
||||
selectedSegmentIndices={selectedSegments[workflow.workflowId] || null}
|
||||
onSegmentClick={onSegmentClick}
|
||||
workflowId={workflow.workflowId}
|
||||
segmentDurationMs={segmentDurationMs}
|
||||
preferBelow={idx < 2}
|
||||
/>
|
||||
</div>
|
||||
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
'use client'
|
||||
|
||||
import { createContext, useContext } from 'react'
|
||||
|
||||
/**
|
||||
* Selection mode for a segment click: plain click, cmd/ctrl+click, or shift+click.
|
||||
*/
|
||||
export type SegmentSelectionMode = 'single' | 'toggle' | 'range'
|
||||
|
||||
export interface DashboardSegmentsContextValue {
|
||||
/** Selected segment indices keyed by workflow id. */
|
||||
selectedSegments: Record<string, number[]>
|
||||
/** Handles a segment click for selecting time segments. */
|
||||
onSegmentClick: (
|
||||
workflowId: string,
|
||||
segmentIndex: number,
|
||||
timestamp: string,
|
||||
mode: SegmentSelectionMode
|
||||
) => void
|
||||
/** Duration of a single segment in milliseconds. */
|
||||
segmentDurationMs: number
|
||||
}
|
||||
|
||||
/**
|
||||
* Feature-local context for dashboard segment selection state, provided by
|
||||
* the dashboard and consumed by StatusBar without threading props through
|
||||
* intermediate components.
|
||||
*/
|
||||
export const DashboardSegmentsContext = createContext<DashboardSegmentsContextValue | null>(null)
|
||||
|
||||
/**
|
||||
* Returns the dashboard segment selection context.
|
||||
* @throws Error when used outside DashboardSegmentsContext.Provider
|
||||
*/
|
||||
export function useDashboardSegments(): DashboardSegmentsContextValue {
|
||||
const context = useContext(DashboardSegmentsContext)
|
||||
if (!context) {
|
||||
throw new Error('useDashboardSegments must be used within DashboardSegmentsContext.Provider')
|
||||
}
|
||||
return context
|
||||
}
|
||||
@@ -4,6 +4,10 @@ import { memo, useCallback, useMemo, useRef, useState } from 'react'
|
||||
import { useParams } from 'next/navigation'
|
||||
import { useShallow } from 'zustand/react/shallow'
|
||||
import { Loader } from '@/components/emcn'
|
||||
import {
|
||||
DashboardSegmentsContext,
|
||||
type SegmentSelectionMode,
|
||||
} from '@/app/workspace/[workspaceId]/logs/components/dashboard/dashboard-segments-context'
|
||||
import { formatLatency } from '@/app/workspace/[workspaceId]/logs/utils'
|
||||
import type { DashboardStatsResponse, WorkflowStats } from '@/hooks/queries/logs'
|
||||
import { useWorkflows } from '@/hooks/queries/workflows'
|
||||
@@ -302,12 +306,7 @@ function DashboardInner({ stats, isLoading, error }: DashboardProps) {
|
||||
* @param mode - Selection mode: 'single', 'toggle' (cmd+click), or 'range' (shift+click)
|
||||
*/
|
||||
const handleSegmentClick = useCallback(
|
||||
(
|
||||
workflowId: string,
|
||||
segmentIndex: number,
|
||||
_timestamp: string,
|
||||
mode: 'single' | 'toggle' | 'range'
|
||||
) => {
|
||||
(workflowId: string, segmentIndex: number, _timestamp: string, mode: SegmentSelectionMode) => {
|
||||
if (mode === 'toggle') {
|
||||
setSelectedSegments((prev) => {
|
||||
const currentSegments = prev[workflowId] || []
|
||||
@@ -355,6 +354,15 @@ function DashboardInner({ stats, isLoading, error }: DashboardProps) {
|
||||
[]
|
||||
)
|
||||
|
||||
const segmentsContextValue = useMemo(
|
||||
() => ({
|
||||
selectedSegments,
|
||||
onSegmentClick: handleSegmentClick,
|
||||
segmentDurationMs: segmentMs,
|
||||
}),
|
||||
[selectedSegments, handleSegmentClick, segmentMs]
|
||||
)
|
||||
|
||||
const resetKey = `${JSON.stringify(stats?.workflows?.map((w) => w.workflowId))}-${timeRange}-${workflowIds.join(',')}-${searchQuery}`
|
||||
const prevResetKeyRef = useRef(resetKey)
|
||||
if (resetKey !== prevResetKeyRef.current) {
|
||||
@@ -477,15 +485,14 @@ function DashboardInner({ stats, isLoading, error }: DashboardProps) {
|
||||
</div>
|
||||
|
||||
<div className='min-h-0 flex-1 overflow-hidden'>
|
||||
<WorkflowsList
|
||||
filteredExecutions={filteredExecutions as WorkflowExecution[]}
|
||||
expandedWorkflowId={expandedWorkflowId}
|
||||
onToggleWorkflow={handleToggleWorkflow}
|
||||
selectedSegments={selectedSegments}
|
||||
onSegmentClick={handleSegmentClick}
|
||||
searchQuery={searchQuery}
|
||||
segmentDurationMs={segmentMs}
|
||||
/>
|
||||
<DashboardSegmentsContext.Provider value={segmentsContextValue}>
|
||||
<WorkflowsList
|
||||
filteredExecutions={filteredExecutions as WorkflowExecution[]}
|
||||
expandedWorkflowId={expandedWorkflowId}
|
||||
onToggleWorkflow={handleToggleWorkflow}
|
||||
searchQuery={searchQuery}
|
||||
/>
|
||||
</DashboardSegmentsContext.Provider>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
+2
-6
@@ -3,7 +3,7 @@ import { Checkbox, Label, Tooltip } from '@/components/emcn'
|
||||
import { formatDisplayText } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/formatted-text'
|
||||
import { getWorkflowSearchLabelHighlight } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/workflow-search-highlight'
|
||||
import { useSubBlockValue } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/hooks/use-sub-block-value'
|
||||
import type { ActiveSearchTarget } from '@/stores/panel/editor/store'
|
||||
import { useActiveSearchTarget } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/providers/active-search-target-provider'
|
||||
|
||||
interface CheckboxListOption {
|
||||
label: string
|
||||
@@ -19,7 +19,6 @@ interface CheckboxListProps {
|
||||
isPreview?: boolean
|
||||
subBlockValues?: Record<string, any>
|
||||
disabled?: boolean
|
||||
activeSearchTarget?: ActiveSearchTarget | null
|
||||
}
|
||||
|
||||
interface CheckboxItemProps {
|
||||
@@ -30,7 +29,6 @@ interface CheckboxItemProps {
|
||||
isPreview: boolean
|
||||
subBlockValues?: Record<string, any>
|
||||
disabled: boolean
|
||||
activeSearchTarget?: ActiveSearchTarget | null
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -49,8 +47,8 @@ function CheckboxItem({
|
||||
isPreview,
|
||||
subBlockValues,
|
||||
disabled,
|
||||
activeSearchTarget,
|
||||
}: CheckboxItemProps) {
|
||||
const activeSearchTarget = useActiveSearchTarget()
|
||||
const [storeValue, setStoreValue] = useSubBlockValue<boolean>(blockId, option.id)
|
||||
const workflowSearchHighlight = getWorkflowSearchLabelHighlight({
|
||||
activeSearchTarget,
|
||||
@@ -105,7 +103,6 @@ export function CheckboxList({
|
||||
isPreview = false,
|
||||
subBlockValues,
|
||||
disabled = false,
|
||||
activeSearchTarget,
|
||||
}: CheckboxListProps) {
|
||||
return (
|
||||
<div className='flex flex-col gap-y-2.5 pt-1'>
|
||||
@@ -119,7 +116,6 @@ export function CheckboxList({
|
||||
isPreview={isPreview}
|
||||
subBlockValues={subBlockValues}
|
||||
disabled={disabled}
|
||||
activeSearchTarget={activeSearchTarget}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
+2
-3
@@ -38,6 +38,7 @@ import {
|
||||
import { getActiveWorkflowSearchHighlight } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/workflow-search-highlight'
|
||||
import { useSubBlockValue } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/hooks/use-sub-block-value'
|
||||
import type { WandControlHandlers } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/sub-block'
|
||||
import { useActiveSearchTarget } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/providers/active-search-target-provider'
|
||||
import { restoreCursorAfterInsertion } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/utils'
|
||||
import { WandPromptBar } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/wand-prompt-bar/wand-prompt-bar'
|
||||
import { useAccessibleReferencePrefixes } from '@/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-accessible-reference-prefixes'
|
||||
@@ -48,7 +49,6 @@ import { createEnvVarPattern, createReferencePattern } from '@/executor/utils/re
|
||||
import { useTagSelection } from '@/hooks/kb/use-tag-selection'
|
||||
import { createShouldHighlightEnvVar, useAvailableEnvVarKeys } from '@/hooks/use-available-env-vars'
|
||||
import { useCodeUndoRedo } from '@/hooks/use-code-undo-redo'
|
||||
import type { ActiveSearchTarget } from '@/stores/panel/editor/store'
|
||||
import { useWorkflowStore } from '@/stores/workflows/workflow/store'
|
||||
|
||||
const logger = createLogger('Code')
|
||||
@@ -213,7 +213,6 @@ interface CodeProps {
|
||||
wandControlRef?: React.MutableRefObject<WandControlHandlers | null>
|
||||
/** Whether to hide the internal wand button (controlled by parent) */
|
||||
hideInternalWand?: boolean
|
||||
activeSearchTarget?: ActiveSearchTarget | null
|
||||
}
|
||||
|
||||
export const Code = memo(function Code({
|
||||
@@ -233,8 +232,8 @@ export const Code = memo(function Code({
|
||||
wandConfig,
|
||||
wandControlRef,
|
||||
hideInternalWand = false,
|
||||
activeSearchTarget,
|
||||
}: CodeProps) {
|
||||
const activeSearchTarget = useActiveSearchTarget()
|
||||
const params = useParams()
|
||||
const workspaceId = params.workspaceId as string
|
||||
|
||||
|
||||
+2
-3
@@ -10,13 +10,13 @@ import { formatDisplayText } from '@/app/workspace/[workspaceId]/w/[workflowId]/
|
||||
import { SubBlockInputController } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/sub-block-input-controller'
|
||||
import { getWorkflowSearchLabelHighlight } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/workflow-search-highlight'
|
||||
import { useSubBlockValue } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/hooks/use-sub-block-value'
|
||||
import { useActiveSearchTarget } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/providers/active-search-target-provider'
|
||||
import { useAccessibleReferencePrefixes } from '@/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-accessible-reference-prefixes'
|
||||
import { getBlock } from '@/blocks/registry'
|
||||
import type { SubBlockConfig } from '@/blocks/types'
|
||||
import { getDependsOnFields } from '@/blocks/utils'
|
||||
import { usePermissionConfig } from '@/hooks/use-permission-config'
|
||||
import { getProviderFromModel } from '@/providers/utils'
|
||||
import type { ActiveSearchTarget } from '@/stores/panel/editor/store'
|
||||
import { useWorkflowRegistry } from '@/stores/workflows/registry/store'
|
||||
import { useSubBlockStore } from '@/stores/workflows/subblock/store'
|
||||
import { useWorkflowStore } from '@/stores/workflows/workflow/store'
|
||||
@@ -70,7 +70,6 @@ interface ComboBoxProps {
|
||||
) => Promise<{ label: string; id: string } | null>
|
||||
/** Field dependencies that trigger option refetch when changed */
|
||||
dependsOn?: SubBlockConfig['dependsOn']
|
||||
activeSearchTarget?: ActiveSearchTarget | null
|
||||
}
|
||||
|
||||
export const ComboBox = memo(function ComboBox({
|
||||
@@ -87,8 +86,8 @@ export const ComboBox = memo(function ComboBox({
|
||||
fetchOptions,
|
||||
fetchOptionById,
|
||||
dependsOn,
|
||||
activeSearchTarget,
|
||||
}: ComboBoxProps) {
|
||||
const activeSearchTarget = useActiveSearchTarget()
|
||||
// Hooks and context
|
||||
const [storeValue, setStoreValue] = useSubBlockValue<string>(blockId, subBlockId)
|
||||
const accessiblePrefixes = useAccessibleReferencePrefixes(blockId)
|
||||
|
||||
+2
-3
@@ -37,13 +37,13 @@ import {
|
||||
} from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/tag-dropdown/tag-dropdown'
|
||||
import { getActiveWorkflowSearchHighlight } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/workflow-search-highlight'
|
||||
import { useSubBlockValue } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/hooks/use-sub-block-value'
|
||||
import { useActiveSearchTarget } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/providers/active-search-target-provider'
|
||||
import { restoreCursorAfterInsertion } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/utils'
|
||||
import { useAccessibleReferencePrefixes } from '@/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-accessible-reference-prefixes'
|
||||
import { normalizeName } from '@/executor/constants'
|
||||
import { createEnvVarPattern, createReferencePattern } from '@/executor/utils/reference-validation'
|
||||
import { useTagSelection } from '@/hooks/kb/use-tag-selection'
|
||||
import { createShouldHighlightEnvVar, useAvailableEnvVarKeys } from '@/hooks/use-available-env-vars'
|
||||
import type { ActiveSearchTarget } from '@/stores/panel/editor/store'
|
||||
import { useWorkflowStore } from '@/stores/workflows/workflow/store'
|
||||
|
||||
const logger = createLogger('ConditionInput')
|
||||
@@ -105,7 +105,6 @@ interface ConditionInputProps {
|
||||
disabled?: boolean
|
||||
/** Mode: 'condition' for code editor, 'router' for text input */
|
||||
mode?: 'condition' | 'router'
|
||||
activeSearchTarget?: ActiveSearchTarget | null
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -134,8 +133,8 @@ export function ConditionInput({
|
||||
previewValue,
|
||||
disabled = false,
|
||||
mode = 'condition',
|
||||
activeSearchTarget,
|
||||
}: ConditionInputProps) {
|
||||
const activeSearchTarget = useActiveSearchTarget()
|
||||
const isRouterMode = mode === 'router'
|
||||
const params = useParams()
|
||||
const workspaceId = params.workspaceId as string
|
||||
|
||||
+2
-3
@@ -21,6 +21,7 @@ import { formatDisplayText } from '@/app/workspace/[workspaceId]/w/[workflowId]/
|
||||
import { getWorkflowSearchLabelHighlight } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/workflow-search-highlight'
|
||||
import { useDependsOnGate } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/hooks/use-depends-on-gate'
|
||||
import { useSubBlockValue } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/hooks/use-sub-block-value'
|
||||
import { useActiveSearchTarget } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/providers/active-search-target-provider'
|
||||
import { getBareIconStyle, type StyleableIcon } from '@/blocks/icon-color'
|
||||
import type { SubBlockConfig } from '@/blocks/types'
|
||||
import { CREDENTIAL_SET } from '@/executor/constants'
|
||||
@@ -30,7 +31,6 @@ import { useOAuthCredentials } from '@/hooks/queries/oauth/oauth-credentials'
|
||||
import { useOrganizations } from '@/hooks/queries/organization'
|
||||
import { useSubscriptionData } from '@/hooks/queries/subscription'
|
||||
import { useCredentialRefreshTriggers } from '@/hooks/use-credential-refresh-triggers'
|
||||
import type { ActiveSearchTarget } from '@/stores/panel/editor/store'
|
||||
import { useWorkflowRegistry } from '@/stores/workflows/registry/store'
|
||||
|
||||
interface CredentialSelectorProps {
|
||||
@@ -40,7 +40,6 @@ interface CredentialSelectorProps {
|
||||
isPreview?: boolean
|
||||
previewValue?: any | null
|
||||
previewContextValues?: Record<string, unknown>
|
||||
activeSearchTarget?: ActiveSearchTarget | null
|
||||
}
|
||||
|
||||
export function CredentialSelector({
|
||||
@@ -50,8 +49,8 @@ export function CredentialSelector({
|
||||
isPreview = false,
|
||||
previewValue,
|
||||
previewContextValues,
|
||||
activeSearchTarget,
|
||||
}: CredentialSelectorProps) {
|
||||
const activeSearchTarget = useActiveSearchTarget()
|
||||
const params = useParams()
|
||||
const workspaceId = (params?.workspaceId as string) || ''
|
||||
const [showConnectModal, setShowConnectModal] = useState(false)
|
||||
|
||||
+2
-3
@@ -21,11 +21,11 @@ import { getActiveWorkflowSearchHighlight } from '@/app/workspace/[workspaceId]/
|
||||
import { useDependsOnGate } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/hooks/use-depends-on-gate'
|
||||
import { useSubBlockInput } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/hooks/use-sub-block-input'
|
||||
import { useSubBlockValue } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/hooks/use-sub-block-value'
|
||||
import { useActiveSearchTarget } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/providers/active-search-target-provider'
|
||||
import { useAccessibleReferencePrefixes } from '@/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-accessible-reference-prefixes'
|
||||
import type { SubBlockConfig } from '@/blocks/types'
|
||||
import { useKnowledgeBaseTagDefinitions } from '@/hooks/kb/use-knowledge-base-tag-definitions'
|
||||
import { useTagSelection } from '@/hooks/kb/use-tag-selection'
|
||||
import type { ActiveSearchTarget } from '@/stores/panel/editor/store'
|
||||
|
||||
interface DocumentTag {
|
||||
id: string
|
||||
@@ -43,7 +43,6 @@ interface DocumentTagEntryProps {
|
||||
isPreview?: boolean
|
||||
previewValue?: any
|
||||
previewContextValues?: Record<string, unknown>
|
||||
activeSearchTarget?: ActiveSearchTarget | null
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -64,8 +63,8 @@ export function DocumentTagEntry({
|
||||
isPreview = false,
|
||||
previewValue,
|
||||
previewContextValues,
|
||||
activeSearchTarget,
|
||||
}: DocumentTagEntryProps) {
|
||||
const activeSearchTarget = useActiveSearchTarget()
|
||||
const [storeValue, setStoreValue] = useSubBlockValue<string>(blockId, subBlock.id)
|
||||
const accessiblePrefixes = useAccessibleReferencePrefixes(blockId)
|
||||
const valueInputRefs = useRef<Record<string, HTMLInputElement>>({})
|
||||
|
||||
+2
-3
@@ -8,11 +8,11 @@ import { buildCanonicalIndex, resolveDependencyValue } from '@/lib/workflows/sub
|
||||
import { formatDisplayText } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/formatted-text'
|
||||
import { getWorkflowSearchLabelHighlight } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/workflow-search-highlight'
|
||||
import { useSubBlockValue } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/hooks/use-sub-block-value'
|
||||
import { useActiveSearchTarget } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/providers/active-search-target-provider'
|
||||
import { getBlock } from '@/blocks/registry'
|
||||
import type { SubBlockConfig } from '@/blocks/types'
|
||||
import { getDependsOnFields } from '@/blocks/utils'
|
||||
import { ResponseBlockHandler } from '@/executor/handlers/response/response-handler'
|
||||
import type { ActiveSearchTarget } from '@/stores/panel/editor/store'
|
||||
import { useWorkflowRegistry } from '@/stores/workflows/registry/store'
|
||||
import { useSubBlockStore } from '@/stores/workflows/subblock/store'
|
||||
import { useWorkflowStore } from '@/stores/workflows/workflow/store'
|
||||
@@ -69,7 +69,6 @@ interface DropdownProps {
|
||||
dependsOn?: SubBlockConfig['dependsOn']
|
||||
/** Enable search input in dropdown */
|
||||
searchable?: boolean
|
||||
activeSearchTarget?: ActiveSearchTarget | null
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -96,8 +95,8 @@ export const Dropdown = memo(function Dropdown({
|
||||
fetchOptionById,
|
||||
dependsOn,
|
||||
searchable = false,
|
||||
activeSearchTarget,
|
||||
}: DropdownProps) {
|
||||
const activeSearchTarget = useActiveSearchTarget()
|
||||
const [storeValue, setStoreValue] = useSubBlockValue<string | string[]>(blockId, subBlockId) as [
|
||||
string | string[] | null | undefined,
|
||||
(value: string | string[]) => void,
|
||||
|
||||
+2
-3
@@ -9,8 +9,8 @@ import { TagDropdown } from '@/app/workspace/[workspaceId]/w/[workflowId]/compon
|
||||
import { getActiveWorkflowSearchHighlight } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/workflow-search-highlight'
|
||||
import { useSubBlockInput } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/hooks/use-sub-block-input'
|
||||
import { useSubBlockValue } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/hooks/use-sub-block-value'
|
||||
import { useActiveSearchTarget } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/providers/active-search-target-provider'
|
||||
import { useAccessibleReferencePrefixes } from '@/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-accessible-reference-prefixes'
|
||||
import type { ActiveSearchTarget } from '@/stores/panel/editor/store'
|
||||
|
||||
interface EvalMetric {
|
||||
id: string
|
||||
@@ -28,7 +28,6 @@ interface EvalInputProps {
|
||||
isPreview?: boolean
|
||||
previewValue?: EvalMetric[] | null
|
||||
disabled?: boolean
|
||||
activeSearchTarget?: ActiveSearchTarget | null
|
||||
}
|
||||
|
||||
// Default values
|
||||
@@ -45,8 +44,8 @@ export function EvalInput({
|
||||
isPreview = false,
|
||||
previewValue,
|
||||
disabled = false,
|
||||
activeSearchTarget,
|
||||
}: EvalInputProps) {
|
||||
const activeSearchTarget = useActiveSearchTarget()
|
||||
const [storeValue, setStoreValue] = useSubBlockValue<EvalMetric[]>(blockId, subBlockId)
|
||||
const accessiblePrefixes = useAccessibleReferencePrefixes(blockId)
|
||||
const descriptionInputRefs = useRef<Record<string, HTMLTextAreaElement>>({})
|
||||
|
||||
+2
-3
@@ -18,12 +18,12 @@ import { getExtensionFromMimeType } from '@/lib/uploads/utils/file-utils'
|
||||
import { formatDisplayText } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/formatted-text'
|
||||
import { getWorkflowSearchLabelHighlight } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/workflow-search-highlight'
|
||||
import { useSubBlockValue } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/hooks/use-sub-block-value'
|
||||
import { useActiveSearchTarget } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/providers/active-search-target-provider'
|
||||
import {
|
||||
useUploadWorkspaceFile,
|
||||
useWorkspaceFiles,
|
||||
workspaceFilesKeys,
|
||||
} from '@/hooks/queries/workspace-files'
|
||||
import type { ActiveSearchTarget } from '@/stores/panel/editor/store'
|
||||
import { useWorkflowRegistry } from '@/stores/workflows/registry/store'
|
||||
import { useWorkflowStore } from '@/stores/workflows/workflow/store'
|
||||
|
||||
@@ -38,7 +38,6 @@ interface FileUploadProps {
|
||||
isPreview?: boolean
|
||||
previewValue?: any | null
|
||||
disabled?: boolean
|
||||
activeSearchTarget?: ActiveSearchTarget | null
|
||||
}
|
||||
|
||||
interface UploadedFile {
|
||||
@@ -165,8 +164,8 @@ export function FileUpload({
|
||||
isPreview = false,
|
||||
previewValue,
|
||||
disabled = false,
|
||||
activeSearchTarget,
|
||||
}: FileUploadProps) {
|
||||
const activeSearchTarget = useActiveSearchTarget()
|
||||
const [storeValue, setStoreValue] = useSubBlockValue(blockId, subBlockId)
|
||||
const [uploadingFiles, setUploadingFiles] = useState<UploadingFile[]>([])
|
||||
const [uploadProgress, setUploadProgress] = useState(0)
|
||||
|
||||
+2
-3
@@ -19,8 +19,8 @@ import {
|
||||
getWorkflowSearchLabelHighlight,
|
||||
} from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/workflow-search-highlight'
|
||||
import type { useSubBlockInput } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/hooks/use-sub-block-input'
|
||||
import { useActiveSearchTarget } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/providers/active-search-target-provider'
|
||||
import { useAccessibleReferencePrefixes } from '@/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-accessible-reference-prefixes'
|
||||
import type { ActiveSearchTarget } from '@/stores/panel/editor/store'
|
||||
|
||||
interface FilterRuleRowProps {
|
||||
blockId: string
|
||||
@@ -38,7 +38,6 @@ interface FilterRuleRowProps {
|
||||
onUpdate: (id: string, field: keyof FilterRule, value: string) => void
|
||||
onToggleCollapse: (id: string) => void
|
||||
inputController: ReturnType<typeof useSubBlockInput>
|
||||
activeSearchTarget?: ActiveSearchTarget | null
|
||||
}
|
||||
|
||||
export function FilterRuleRow({
|
||||
@@ -55,8 +54,8 @@ export function FilterRuleRow({
|
||||
onUpdate,
|
||||
onToggleCollapse,
|
||||
inputController,
|
||||
activeSearchTarget,
|
||||
}: FilterRuleRowProps) {
|
||||
const activeSearchTarget = useActiveSearchTarget()
|
||||
const accessiblePrefixes = useAccessibleReferencePrefixes(blockId)
|
||||
const valueInputRef = useRef<HTMLInputElement>(null)
|
||||
const overlayRef = useRef<HTMLDivElement>(null)
|
||||
|
||||
+2
-4
@@ -9,7 +9,7 @@ import { useFilterBuilder } from '@/lib/table/query-builder/use-query-builder'
|
||||
import { useCanonicalSubBlockValue } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/hooks/use-canonical-sub-block-value'
|
||||
import { useSubBlockInput } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/hooks/use-sub-block-input'
|
||||
import { useSubBlockValue } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/hooks/use-sub-block-value'
|
||||
import type { ActiveSearchTarget } from '@/stores/panel/editor/store'
|
||||
import { useActiveSearchTarget } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/providers/active-search-target-provider'
|
||||
import { FilterRuleRow } from './components/filter-rule-row'
|
||||
|
||||
interface FilterBuilderProps {
|
||||
@@ -20,7 +20,6 @@ interface FilterBuilderProps {
|
||||
disabled?: boolean
|
||||
columns?: Array<{ value: string; label: string }>
|
||||
tableIdSubBlockId?: string
|
||||
activeSearchTarget?: ActiveSearchTarget | null
|
||||
}
|
||||
|
||||
const createDefaultRule = (columns: ComboboxOption[]): FilterRule => ({
|
||||
@@ -41,8 +40,8 @@ export function FilterBuilder({
|
||||
disabled = false,
|
||||
columns: propColumns,
|
||||
tableIdSubBlockId = 'tableId',
|
||||
activeSearchTarget,
|
||||
}: FilterBuilderProps) {
|
||||
const activeSearchTarget = useActiveSearchTarget()
|
||||
const [storeValue, setStoreValue] = useSubBlockValue<FilterRule[]>(blockId, subBlockId)
|
||||
const tableIdValue = useCanonicalSubBlockValue<string>(blockId, tableIdSubBlockId)
|
||||
|
||||
@@ -120,7 +119,6 @@ export function FilterBuilder({
|
||||
onUpdate={updateRule}
|
||||
onToggleCollapse={toggleCollapse}
|
||||
inputController={inputController}
|
||||
activeSearchTarget={activeSearchTarget}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
|
||||
+2
-3
@@ -14,7 +14,7 @@ import { cn } from '@/lib/core/utils/cn'
|
||||
import { formatDisplayText } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/formatted-text'
|
||||
import { getWorkflowSearchLabelHighlight } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/workflow-search-highlight'
|
||||
import { useSubBlockValue } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/hooks/use-sub-block-value'
|
||||
import type { ActiveSearchTarget } from '@/stores/panel/editor/store'
|
||||
import { useActiveSearchTarget } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/providers/active-search-target-provider'
|
||||
|
||||
interface SelectedCountDisplayProps {
|
||||
noneSelected: boolean
|
||||
@@ -49,7 +49,6 @@ interface GroupedCheckboxListProps {
|
||||
subBlockValues: Record<string, any>
|
||||
disabled?: boolean
|
||||
maxHeight?: number
|
||||
activeSearchTarget?: ActiveSearchTarget | null
|
||||
}
|
||||
|
||||
export function GroupedCheckboxList({
|
||||
@@ -61,8 +60,8 @@ export function GroupedCheckboxList({
|
||||
subBlockValues,
|
||||
disabled = false,
|
||||
maxHeight = 400,
|
||||
activeSearchTarget,
|
||||
}: GroupedCheckboxListProps) {
|
||||
const activeSearchTarget = useActiveSearchTarget()
|
||||
const [open, setOpen] = useState(false)
|
||||
const [storeValue, setStoreValue] = useSubBlockValue(blockId, subBlockId)
|
||||
const optionRefs = useRef<Record<number, HTMLDivElement | null>>({})
|
||||
|
||||
+2
-3
@@ -8,10 +8,10 @@ import { getActiveWorkflowSearchHighlight } from '@/app/workspace/[workspaceId]/
|
||||
import { useDependsOnGate } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/hooks/use-depends-on-gate'
|
||||
import { useSubBlockInput } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/hooks/use-sub-block-input'
|
||||
import { useSubBlockValue } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/hooks/use-sub-block-value'
|
||||
import { useActiveSearchTarget } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/providers/active-search-target-provider'
|
||||
import { useAccessibleReferencePrefixes } from '@/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-accessible-reference-prefixes'
|
||||
import type { SubBlockConfig } from '@/blocks/types'
|
||||
import { useWorkflowState } from '@/hooks/queries/workflows'
|
||||
import type { ActiveSearchTarget } from '@/stores/panel/editor/store'
|
||||
|
||||
/**
|
||||
* Props for the InputMappingField component
|
||||
@@ -43,7 +43,6 @@ interface InputMappingProps {
|
||||
disabled?: boolean
|
||||
/** Sub-block values from the preview context for resolving sibling sub-block values */
|
||||
previewContextValues?: Record<string, unknown>
|
||||
activeSearchTarget?: ActiveSearchTarget | null
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -58,8 +57,8 @@ export function InputMapping({
|
||||
previewValue,
|
||||
disabled = false,
|
||||
previewContextValues,
|
||||
activeSearchTarget,
|
||||
}: InputMappingProps) {
|
||||
const activeSearchTarget = useActiveSearchTarget()
|
||||
const subBlockId = subBlock.id
|
||||
const [mapping, setMapping] = useSubBlockValue(blockId, subBlockId)
|
||||
const { dependencyValues } = useDependsOnGate(blockId, subBlock, {
|
||||
|
||||
+2
-3
@@ -10,10 +10,10 @@ import type { KnowledgeBaseData } from '@/lib/knowledge/types'
|
||||
import { formatDisplayText } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/formatted-text'
|
||||
import { getWorkflowSearchLabelHighlight } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/workflow-search-highlight'
|
||||
import { useSubBlockValue } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/hooks/use-sub-block-value'
|
||||
import { useActiveSearchTarget } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/providers/active-search-target-provider'
|
||||
import type { SubBlockConfig } from '@/blocks/types'
|
||||
import { useKnowledgeBasesList } from '@/hooks/kb/use-knowledge'
|
||||
import { fetchKnowledgeBase, knowledgeKeys } from '@/hooks/queries/kb/knowledge'
|
||||
import type { ActiveSearchTarget } from '@/stores/panel/editor/store'
|
||||
|
||||
interface KnowledgeBaseSelectorProps {
|
||||
blockId: string
|
||||
@@ -22,7 +22,6 @@ interface KnowledgeBaseSelectorProps {
|
||||
onKnowledgeBaseSelect?: (knowledgeBaseId: string | string[]) => void
|
||||
isPreview?: boolean
|
||||
previewValue?: string | null
|
||||
activeSearchTarget?: ActiveSearchTarget | null
|
||||
}
|
||||
|
||||
export function KnowledgeBaseSelector({
|
||||
@@ -32,8 +31,8 @@ export function KnowledgeBaseSelector({
|
||||
onKnowledgeBaseSelect,
|
||||
isPreview = false,
|
||||
previewValue,
|
||||
activeSearchTarget,
|
||||
}: KnowledgeBaseSelectorProps) {
|
||||
const activeSearchTarget = useActiveSearchTarget()
|
||||
const params = useParams()
|
||||
const workspaceId = params.workspaceId as string
|
||||
|
||||
|
||||
+2
-3
@@ -21,11 +21,11 @@ import { TagDropdown } from '@/app/workspace/[workspaceId]/w/[workflowId]/compon
|
||||
import { getActiveWorkflowSearchHighlight } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/workflow-search-highlight'
|
||||
import { useDependsOnGate } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/hooks/use-depends-on-gate'
|
||||
import { useSubBlockInput } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/hooks/use-sub-block-input'
|
||||
import { useActiveSearchTarget } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/providers/active-search-target-provider'
|
||||
import { useAccessibleReferencePrefixes } from '@/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-accessible-reference-prefixes'
|
||||
import type { SubBlockConfig } from '@/blocks/types'
|
||||
import { useKnowledgeBaseTagDefinitions } from '@/hooks/kb/use-knowledge-base-tag-definitions'
|
||||
import { useTagSelection } from '@/hooks/kb/use-tag-selection'
|
||||
import type { ActiveSearchTarget } from '@/stores/panel/editor/store'
|
||||
import { useSubBlockValue } from '../../hooks/use-sub-block-value'
|
||||
|
||||
interface TagFilter {
|
||||
@@ -46,7 +46,6 @@ interface KnowledgeTagFiltersProps {
|
||||
isPreview?: boolean
|
||||
previewValue?: string | null
|
||||
previewContextValues?: Record<string, unknown>
|
||||
activeSearchTarget?: ActiveSearchTarget | null
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -68,8 +67,8 @@ export function KnowledgeTagFilters({
|
||||
isPreview = false,
|
||||
previewValue,
|
||||
previewContextValues,
|
||||
activeSearchTarget,
|
||||
}: KnowledgeTagFiltersProps) {
|
||||
const activeSearchTarget = useActiveSearchTarget()
|
||||
const [storeValue, setStoreValue] = useSubBlockValue<string | null>(blockId, subBlock.id)
|
||||
const emitTagSelection = useTagSelection(blockId, subBlock.id)
|
||||
const valueInputRefs = useRef<Record<string, HTMLInputElement>>({})
|
||||
|
||||
+2
-3
@@ -18,11 +18,11 @@ import { getActiveWorkflowSearchHighlight } from '@/app/workspace/[workspaceId]/
|
||||
import { useSubBlockInput } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/hooks/use-sub-block-input'
|
||||
import { useSubBlockValue } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/hooks/use-sub-block-value'
|
||||
import type { WandControlHandlers } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/sub-block'
|
||||
import { useActiveSearchTarget } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/providers/active-search-target-provider'
|
||||
import { WandPromptBar } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/wand-prompt-bar/wand-prompt-bar'
|
||||
import { useAccessibleReferencePrefixes } from '@/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-accessible-reference-prefixes'
|
||||
import { useWand } from '@/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-wand'
|
||||
import type { SubBlockConfig } from '@/blocks/types'
|
||||
import type { ActiveSearchTarget } from '@/stores/panel/editor/store'
|
||||
|
||||
const logger = createLogger('LongInput')
|
||||
|
||||
@@ -69,7 +69,6 @@ interface LongInputProps {
|
||||
wandControlRef?: React.MutableRefObject<WandControlHandlers | null>
|
||||
/** Whether to hide the internal wand button (controlled by parent) */
|
||||
hideInternalWand?: boolean
|
||||
activeSearchTarget?: ActiveSearchTarget | null
|
||||
workflowSearchValuePath?: Array<string | number>
|
||||
}
|
||||
|
||||
@@ -96,9 +95,9 @@ export function LongInput({
|
||||
disabled,
|
||||
wandControlRef,
|
||||
hideInternalWand = false,
|
||||
activeSearchTarget,
|
||||
workflowSearchValuePath = [],
|
||||
}: LongInputProps) {
|
||||
const activeSearchTarget = useActiveSearchTarget()
|
||||
// Local state for immediate UI updates during streaming
|
||||
const [localContent, setLocalContent] = useState<string>('')
|
||||
const persistSubBlockValueRef = useRef<(value: string) => void>(() => {})
|
||||
|
||||
+2
-5
@@ -8,9 +8,9 @@ import { ShortInput } from '@/app/workspace/[workspaceId]/w/[workflowId]/compone
|
||||
import { getWorkflowSearchLabelHighlight } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/workflow-search-highlight'
|
||||
import { useSubBlockValue } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/hooks/use-sub-block-value'
|
||||
import { resolvePreviewContextValue } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/utils'
|
||||
import { useActiveSearchTarget } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/providers/active-search-target-provider'
|
||||
import type { SubBlockConfig } from '@/blocks/types'
|
||||
import { useMcpTools } from '@/hooks/mcp/use-mcp-tools'
|
||||
import type { ActiveSearchTarget } from '@/stores/panel/editor/store'
|
||||
import { formatParameterLabel } from '@/tools/params'
|
||||
|
||||
const logger = createLogger('McpDynamicArgs')
|
||||
@@ -22,7 +22,6 @@ interface McpDynamicArgsProps {
|
||||
isPreview?: boolean
|
||||
previewValue?: any
|
||||
previewContextValues?: Record<string, unknown>
|
||||
activeSearchTarget?: ActiveSearchTarget | null
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -53,8 +52,8 @@ export function McpDynamicArgs({
|
||||
isPreview = false,
|
||||
previewValue,
|
||||
previewContextValues,
|
||||
activeSearchTarget,
|
||||
}: McpDynamicArgsProps) {
|
||||
const activeSearchTarget = useActiveSearchTarget()
|
||||
const params = useParams()
|
||||
const workspaceId = params.workspaceId as string
|
||||
const { mcpTools, isLoading } = useMcpTools(workspaceId)
|
||||
@@ -254,7 +253,6 @@ export function McpDynamicArgs({
|
||||
onChange={(newValue) => updateParameter(paramName, newValue)}
|
||||
isPreview={isPreview}
|
||||
disabled={disabled}
|
||||
activeSearchTarget={activeSearchTarget}
|
||||
workflowSearchValuePath={[paramName]}
|
||||
/>
|
||||
)
|
||||
@@ -295,7 +293,6 @@ export function McpDynamicArgs({
|
||||
}}
|
||||
isPreview={isPreview}
|
||||
disabled={disabled}
|
||||
activeSearchTarget={activeSearchTarget}
|
||||
workflowSearchValuePath={[paramName]}
|
||||
/>
|
||||
)
|
||||
|
||||
+2
-3
@@ -6,9 +6,9 @@ import { Combobox } from '@/components/emcn/components'
|
||||
import { formatDisplayText } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/formatted-text'
|
||||
import { getWorkflowSearchLabelHighlight } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/workflow-search-highlight'
|
||||
import { useSubBlockValue } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/hooks/use-sub-block-value'
|
||||
import { useActiveSearchTarget } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/providers/active-search-target-provider'
|
||||
import type { SubBlockConfig } from '@/blocks/types'
|
||||
import { useMcpServers } from '@/hooks/queries/mcp'
|
||||
import type { ActiveSearchTarget } from '@/stores/panel/editor/store'
|
||||
|
||||
interface McpServerSelectorProps {
|
||||
blockId: string
|
||||
@@ -16,7 +16,6 @@ interface McpServerSelectorProps {
|
||||
disabled?: boolean
|
||||
isPreview?: boolean
|
||||
previewValue?: string | null
|
||||
activeSearchTarget?: ActiveSearchTarget | null
|
||||
}
|
||||
|
||||
export function McpServerSelector({
|
||||
@@ -25,8 +24,8 @@ export function McpServerSelector({
|
||||
disabled = false,
|
||||
isPreview = false,
|
||||
previewValue,
|
||||
activeSearchTarget,
|
||||
}: McpServerSelectorProps) {
|
||||
const activeSearchTarget = useActiveSearchTarget()
|
||||
const params = useParams()
|
||||
const workspaceId = params.workspaceId as string
|
||||
const [inputValue, setInputValue] = useState('')
|
||||
|
||||
+2
-3
@@ -7,9 +7,9 @@ import { formatDisplayText } from '@/app/workspace/[workspaceId]/w/[workflowId]/
|
||||
import { getWorkflowSearchLabelHighlight } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/workflow-search-highlight'
|
||||
import { useSubBlockValue } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/hooks/use-sub-block-value'
|
||||
import { resolvePreviewContextValue } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/utils'
|
||||
import { useActiveSearchTarget } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/providers/active-search-target-provider'
|
||||
import type { SubBlockConfig } from '@/blocks/types'
|
||||
import { useMcpTools } from '@/hooks/mcp/use-mcp-tools'
|
||||
import type { ActiveSearchTarget } from '@/stores/panel/editor/store'
|
||||
|
||||
interface McpToolSelectorProps {
|
||||
blockId: string
|
||||
@@ -18,7 +18,6 @@ interface McpToolSelectorProps {
|
||||
isPreview?: boolean
|
||||
previewValue?: string | null
|
||||
previewContextValues?: Record<string, unknown>
|
||||
activeSearchTarget?: ActiveSearchTarget | null
|
||||
}
|
||||
|
||||
export function McpToolSelector({
|
||||
@@ -28,8 +27,8 @@ export function McpToolSelector({
|
||||
isPreview = false,
|
||||
previewValue,
|
||||
previewContextValues,
|
||||
activeSearchTarget,
|
||||
}: McpToolSelectorProps) {
|
||||
const activeSearchTarget = useActiveSearchTarget()
|
||||
const params = useParams()
|
||||
const workspaceId = params.workspaceId as string
|
||||
const [inputValue, setInputValue] = useState('')
|
||||
|
||||
+2
-3
@@ -20,10 +20,10 @@ import { getActiveWorkflowSearchHighlight } from '@/app/workspace/[workspaceId]/
|
||||
import { useSubBlockInput } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/hooks/use-sub-block-input'
|
||||
import { useSubBlockValue } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/hooks/use-sub-block-value'
|
||||
import type { WandControlHandlers } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/sub-block'
|
||||
import { useActiveSearchTarget } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/providers/active-search-target-provider'
|
||||
import { useAccessibleReferencePrefixes } from '@/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-accessible-reference-prefixes'
|
||||
import { useWand } from '@/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-wand'
|
||||
import type { SubBlockConfig } from '@/blocks/types'
|
||||
import type { ActiveSearchTarget } from '@/stores/panel/editor/store'
|
||||
|
||||
const MIN_TEXTAREA_HEIGHT_PX = 80
|
||||
const MAX_TEXTAREA_HEIGHT_PX = 320
|
||||
@@ -70,7 +70,6 @@ interface MessagesInputProps {
|
||||
disabled?: boolean
|
||||
/** Ref to expose wand control handlers to parent */
|
||||
wandControlRef?: React.MutableRefObject<WandControlHandlers | null>
|
||||
activeSearchTarget?: ActiveSearchTarget | null
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -89,8 +88,8 @@ export function MessagesInput({
|
||||
previewValue,
|
||||
disabled = false,
|
||||
wandControlRef,
|
||||
activeSearchTarget,
|
||||
}: MessagesInputProps) {
|
||||
const activeSearchTarget = useActiveSearchTarget()
|
||||
const [messages, setMessages] = useSubBlockValue<Message[]>(blockId, subBlockId, false)
|
||||
const [localMessages, setLocalMessages] = useState<Message[]>([{ role: 'user', content: '' }])
|
||||
const messageIdsRef = useRef<string[]>([generateShortId()])
|
||||
|
||||
-4
@@ -1,5 +1,4 @@
|
||||
import { ResponseFormat as SharedResponseFormat } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/starter/input-format'
|
||||
import type { ActiveSearchTarget } from '@/stores/panel/editor/store'
|
||||
|
||||
interface ResponseFormatProps {
|
||||
blockId: string
|
||||
@@ -8,7 +7,6 @@ interface ResponseFormatProps {
|
||||
previewValue?: any
|
||||
disabled?: boolean
|
||||
config?: any
|
||||
activeSearchTarget?: ActiveSearchTarget | null
|
||||
}
|
||||
|
||||
export function ResponseFormat({
|
||||
@@ -18,7 +16,6 @@ export function ResponseFormat({
|
||||
previewValue,
|
||||
disabled = false,
|
||||
config,
|
||||
activeSearchTarget,
|
||||
}: ResponseFormatProps) {
|
||||
return (
|
||||
<SharedResponseFormat
|
||||
@@ -28,7 +25,6 @@ export function ResponseFormat({
|
||||
previewValue={previewValue}
|
||||
disabled={disabled}
|
||||
config={config}
|
||||
activeSearchTarget={activeSearchTarget}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
+2
-3
@@ -6,6 +6,7 @@ import { formatDisplayText } from '@/app/workspace/[workspaceId]/w/[workflowId]/
|
||||
import { SubBlockInputController } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/sub-block-input-controller'
|
||||
import { getWorkflowSearchLabelHighlight } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/workflow-search-highlight'
|
||||
import { useSubBlockValue } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/hooks/use-sub-block-value'
|
||||
import { useActiveSearchTarget } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/providers/active-search-target-provider'
|
||||
import type { SubBlockConfig } from '@/blocks/types'
|
||||
import type { SelectorContext, SelectorKey } from '@/hooks/selectors/types'
|
||||
import {
|
||||
@@ -13,7 +14,6 @@ import {
|
||||
useSelectorOptionMap,
|
||||
useSelectorOptions,
|
||||
} from '@/hooks/selectors/use-selector-query'
|
||||
import type { ActiveSearchTarget } from '@/stores/panel/editor/store'
|
||||
|
||||
interface SelectorComboboxProps {
|
||||
blockId: string
|
||||
@@ -28,7 +28,6 @@ interface SelectorComboboxProps {
|
||||
onOptionChange?: (value: string) => void
|
||||
allowSearch?: boolean
|
||||
missingOptionLabel?: string
|
||||
activeSearchTarget?: ActiveSearchTarget | null
|
||||
}
|
||||
|
||||
export function SelectorCombobox({
|
||||
@@ -44,8 +43,8 @@ export function SelectorCombobox({
|
||||
onOptionChange,
|
||||
allowSearch = true,
|
||||
missingOptionLabel,
|
||||
activeSearchTarget,
|
||||
}: SelectorComboboxProps) {
|
||||
const activeSearchTarget = useActiveSearchTarget()
|
||||
const [storeValueRaw, setStoreValue] = useSubBlockValue<string | null | undefined>(
|
||||
blockId,
|
||||
subBlock.id
|
||||
|
||||
-4
@@ -8,7 +8,6 @@ import { useSubBlockValue } from '@/app/workspace/[workspaceId]/w/[workflowId]/c
|
||||
import type { SubBlockConfig } from '@/blocks/types'
|
||||
import type { SelectorContext } from '@/hooks/selectors/types'
|
||||
import { useCollaborativeWorkflow } from '@/hooks/use-collaborative-workflow'
|
||||
import type { ActiveSearchTarget } from '@/stores/panel/editor/store'
|
||||
|
||||
export interface SelectorOverrides {
|
||||
transformContext?: (context: SelectorContext, deps: Record<string, unknown>) => SelectorContext
|
||||
@@ -23,7 +22,6 @@ interface SelectorInputProps {
|
||||
previewValue?: any
|
||||
previewContextValues?: Record<string, any>
|
||||
overrides?: SelectorOverrides
|
||||
activeSearchTarget?: ActiveSearchTarget | null
|
||||
}
|
||||
|
||||
export function SelectorInput({
|
||||
@@ -34,7 +32,6 @@ export function SelectorInput({
|
||||
previewValue,
|
||||
previewContextValues,
|
||||
overrides,
|
||||
activeSearchTarget,
|
||||
}: SelectorInputProps) {
|
||||
const { collaborativeSetSubblockValue } = useCollaborativeWorkflow()
|
||||
const [storeValue] = useSubBlockValue(blockId, subBlock.id)
|
||||
@@ -104,7 +101,6 @@ export function SelectorInput({
|
||||
collaborativeSetSubblockValue(blockId, subBlock.id, value)
|
||||
}
|
||||
}}
|
||||
activeSearchTarget={activeSearchTarget}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
+2
-3
@@ -12,12 +12,12 @@ import { SubBlockInputController } from '@/app/workspace/[workspaceId]/w/[workfl
|
||||
import { getActiveWorkflowSearchHighlight } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/workflow-search-highlight'
|
||||
import { useSubBlockValue } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/hooks/use-sub-block-value'
|
||||
import type { WandControlHandlers } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/sub-block'
|
||||
import { useActiveSearchTarget } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/providers/active-search-target-provider'
|
||||
import { WandPromptBar } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/wand-prompt-bar/wand-prompt-bar'
|
||||
import { useAccessibleReferencePrefixes } from '@/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-accessible-reference-prefixes'
|
||||
import { useWand } from '@/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-wand'
|
||||
import type { SubBlockConfig } from '@/blocks/types'
|
||||
import { useWebhookManagement } from '@/hooks/use-webhook-management'
|
||||
import type { ActiveSearchTarget } from '@/stores/panel/editor/store'
|
||||
|
||||
/**
|
||||
* Props for the ShortInput component
|
||||
@@ -53,7 +53,6 @@ interface ShortInputProps {
|
||||
hideInternalWand?: boolean
|
||||
/** Whether workflow search is actively highlighting this input */
|
||||
isSearchHighlighted?: boolean
|
||||
activeSearchTarget?: ActiveSearchTarget | null
|
||||
workflowSearchValuePath?: Array<string | number>
|
||||
}
|
||||
|
||||
@@ -84,9 +83,9 @@ export const ShortInput = memo(function ShortInput({
|
||||
wandControlRef,
|
||||
hideInternalWand = false,
|
||||
isSearchHighlighted = false,
|
||||
activeSearchTarget,
|
||||
workflowSearchValuePath = [],
|
||||
}: ShortInputProps) {
|
||||
const activeSearchTarget = useActiveSearchTarget()
|
||||
const [localContent, setLocalContent] = useState<string>('')
|
||||
const [isFocused, setIsFocused] = useState(false)
|
||||
const persistSubBlockValueRef = useRef<(value: string) => void>(() => {})
|
||||
|
||||
+2
-3
@@ -9,10 +9,10 @@ import { SkillModal } from '@/app/workspace/[workspaceId]/skills/components/skil
|
||||
import { formatDisplayText } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/formatted-text'
|
||||
import { getWorkflowSearchLabelHighlight } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/workflow-search-highlight'
|
||||
import { useSubBlockValue } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/hooks/use-sub-block-value'
|
||||
import { useActiveSearchTarget } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/providers/active-search-target-provider'
|
||||
import type { SkillDefinition } from '@/hooks/queries/skills'
|
||||
import { useSkills } from '@/hooks/queries/skills'
|
||||
import { usePermissionConfig } from '@/hooks/use-permission-config'
|
||||
import type { ActiveSearchTarget } from '@/stores/panel/editor/store'
|
||||
|
||||
interface StoredSkill {
|
||||
skillId: string
|
||||
@@ -25,7 +25,6 @@ interface SkillInputProps {
|
||||
isPreview?: boolean
|
||||
previewValue?: unknown
|
||||
disabled?: boolean
|
||||
activeSearchTarget?: ActiveSearchTarget | null
|
||||
}
|
||||
|
||||
export function SkillInput({
|
||||
@@ -34,8 +33,8 @@ export function SkillInput({
|
||||
isPreview,
|
||||
previewValue,
|
||||
disabled,
|
||||
activeSearchTarget,
|
||||
}: SkillInputProps) {
|
||||
const activeSearchTarget = useActiveSearchTarget()
|
||||
const params = useParams()
|
||||
const workspaceId = params.workspaceId as string
|
||||
|
||||
|
||||
+2
-3
@@ -5,7 +5,7 @@ import { handleKeyboardActivation } from '@/lib/core/utils/keyboard'
|
||||
import type { SortRule } from '@/lib/table/query-builder/constants'
|
||||
import { formatDisplayText } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/formatted-text'
|
||||
import { getWorkflowSearchLabelHighlight } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/workflow-search-highlight'
|
||||
import type { ActiveSearchTarget } from '@/stores/panel/editor/store'
|
||||
import { useActiveSearchTarget } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/providers/active-search-target-provider'
|
||||
|
||||
interface SortRuleRowProps {
|
||||
rule: SortRule
|
||||
@@ -15,7 +15,6 @@ interface SortRuleRowProps {
|
||||
isReadOnly: boolean
|
||||
blockId: string
|
||||
subBlockId: string
|
||||
activeSearchTarget?: ActiveSearchTarget | null
|
||||
onAdd: () => void
|
||||
onRemove: (id: string) => void
|
||||
onUpdate: (id: string, field: keyof SortRule, value: string) => void
|
||||
@@ -30,12 +29,12 @@ export function SortRuleRow({
|
||||
isReadOnly,
|
||||
blockId,
|
||||
subBlockId,
|
||||
activeSearchTarget,
|
||||
onAdd,
|
||||
onRemove,
|
||||
onUpdate,
|
||||
onToggleCollapse,
|
||||
}: SortRuleRowProps) {
|
||||
const activeSearchTarget = useActiveSearchTarget()
|
||||
const getDirectionLabel = (value: string) => {
|
||||
const option = directionOptions.find((dir) => dir.value === value)
|
||||
return option?.label || value
|
||||
|
||||
-4
@@ -7,7 +7,6 @@ import { useTableColumns } from '@/lib/table/hooks'
|
||||
import { SORT_DIRECTIONS, type SortRule } from '@/lib/table/query-builder/constants'
|
||||
import { useCanonicalSubBlockValue } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/hooks/use-canonical-sub-block-value'
|
||||
import { useSubBlockValue } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/hooks/use-sub-block-value'
|
||||
import type { ActiveSearchTarget } from '@/stores/panel/editor/store'
|
||||
import { SortRuleRow } from './components/sort-rule-row'
|
||||
|
||||
interface SortBuilderProps {
|
||||
@@ -18,7 +17,6 @@ interface SortBuilderProps {
|
||||
disabled?: boolean
|
||||
columns?: Array<{ value: string; label: string }>
|
||||
tableIdSubBlockId?: string
|
||||
activeSearchTarget?: ActiveSearchTarget | null
|
||||
}
|
||||
|
||||
const createDefaultRule = (columns: ComboboxOption[]): SortRule => ({
|
||||
@@ -37,7 +35,6 @@ export function SortBuilder({
|
||||
disabled = false,
|
||||
columns: propColumns,
|
||||
tableIdSubBlockId = 'tableId',
|
||||
activeSearchTarget,
|
||||
}: SortBuilderProps) {
|
||||
const [storeValue, setStoreValue] = useSubBlockValue<SortRule[]>(blockId, subBlockId)
|
||||
const tableIdValue = useCanonicalSubBlockValue<string>(blockId, tableIdSubBlockId)
|
||||
@@ -103,7 +100,6 @@ export function SortBuilder({
|
||||
isReadOnly={isReadOnly}
|
||||
blockId={blockId}
|
||||
subBlockId={subBlockId}
|
||||
activeSearchTarget={activeSearchTarget}
|
||||
onAdd={addRule}
|
||||
onRemove={removeRule}
|
||||
onUpdate={updateRule}
|
||||
|
||||
+2
-3
@@ -26,8 +26,8 @@ import { TagDropdown } from '@/app/workspace/[workspaceId]/w/[workflowId]/compon
|
||||
import { getActiveWorkflowSearchHighlight } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/workflow-search-highlight'
|
||||
import { useSubBlockInput } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/hooks/use-sub-block-input'
|
||||
import { useSubBlockValue } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/hooks/use-sub-block-value'
|
||||
import { useActiveSearchTarget } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/providers/active-search-target-provider'
|
||||
import { useAccessibleReferencePrefixes } from '@/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-accessible-reference-prefixes'
|
||||
import type { ActiveSearchTarget } from '@/stores/panel/editor/store'
|
||||
|
||||
interface Field {
|
||||
id: string
|
||||
@@ -52,7 +52,6 @@ interface FieldFormatProps {
|
||||
valuePlaceholder?: string
|
||||
descriptionPlaceholder?: string
|
||||
config?: any
|
||||
activeSearchTarget?: ActiveSearchTarget | null
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -107,8 +106,8 @@ export function FieldFormat({
|
||||
showDescription = false,
|
||||
valuePlaceholder = 'Enter default value',
|
||||
descriptionPlaceholder = 'Describe this field',
|
||||
activeSearchTarget,
|
||||
}: FieldFormatProps) {
|
||||
const activeSearchTarget = useActiveSearchTarget()
|
||||
const [storeValue, setStoreValue] = useSubBlockValue<Field[]>(blockId, subBlockId)
|
||||
const valueInputRefs = useRef<Record<string, HTMLInputElement | HTMLTextAreaElement>>({})
|
||||
const nameInputRefs = useRef<Record<string, HTMLInputElement>>({})
|
||||
|
||||
-4
@@ -33,8 +33,6 @@ export interface SubBlockInputControllerProps {
|
||||
onStreamingEnd?: () => void
|
||||
/** Optional preview value for read-only preview. */
|
||||
previewValue?: string | null
|
||||
/** Optional workspace id for env var scoping. */
|
||||
workspaceId?: string
|
||||
/**
|
||||
* Optional callback to force/show the env var dropdown (e.g., API key fields).
|
||||
* Return { show: true, searchTerm?: string } to override defaults.
|
||||
@@ -84,7 +82,6 @@ export function SubBlockInputController(props: SubBlockInputControllerProps): Re
|
||||
isStreaming,
|
||||
onStreamingEnd,
|
||||
previewValue,
|
||||
workspaceId,
|
||||
shouldForceEnvDropdown,
|
||||
shouldForceTagDropdown,
|
||||
children,
|
||||
@@ -101,7 +98,6 @@ export function SubBlockInputController(props: SubBlockInputControllerProps): Re
|
||||
isStreaming,
|
||||
onStreamingEnd,
|
||||
previewValue,
|
||||
workspaceId,
|
||||
shouldForceEnvDropdown,
|
||||
shouldForceTagDropdown,
|
||||
})
|
||||
|
||||
+2
-3
@@ -6,9 +6,9 @@ import { Combobox, type ComboboxOption } from '@/components/emcn'
|
||||
import { formatDisplayText } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/formatted-text'
|
||||
import { getWorkflowSearchLabelHighlight } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/workflow-search-highlight'
|
||||
import { useSubBlockValue } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/hooks/use-sub-block-value'
|
||||
import { useActiveSearchTarget } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/providers/active-search-target-provider'
|
||||
import type { SubBlockConfig } from '@/blocks/types'
|
||||
import { useTablesList } from '@/hooks/queries/tables'
|
||||
import type { ActiveSearchTarget } from '@/stores/panel/editor/store'
|
||||
|
||||
interface TableSelectorProps {
|
||||
blockId: string
|
||||
@@ -16,7 +16,6 @@ interface TableSelectorProps {
|
||||
disabled?: boolean
|
||||
isPreview?: boolean
|
||||
previewValue?: string | null
|
||||
activeSearchTarget?: ActiveSearchTarget | null
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -33,8 +32,8 @@ export function TableSelector({
|
||||
disabled = false,
|
||||
isPreview = false,
|
||||
previewValue,
|
||||
activeSearchTarget,
|
||||
}: TableSelectorProps) {
|
||||
const activeSearchTarget = useActiveSearchTarget()
|
||||
const params = useParams()
|
||||
const workspaceId = params.workspaceId as string
|
||||
|
||||
|
||||
+3
-6
@@ -14,8 +14,8 @@ import {
|
||||
} from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/workflow-search-highlight'
|
||||
import { useSubBlockInput } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/hooks/use-sub-block-input'
|
||||
import { useSubBlockValue } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/hooks/use-sub-block-value'
|
||||
import { useActiveSearchTarget } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/providers/active-search-target-provider'
|
||||
import { useAccessibleReferencePrefixes } from '@/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-accessible-reference-prefixes'
|
||||
import type { ActiveSearchTarget } from '@/stores/panel/editor/store'
|
||||
|
||||
const logger = createLogger('Table')
|
||||
|
||||
@@ -26,7 +26,6 @@ interface TableProps {
|
||||
isPreview?: boolean
|
||||
previewValue?: WorkflowTableRow[] | null
|
||||
disabled?: boolean
|
||||
activeSearchTarget?: ActiveSearchTarget | null
|
||||
}
|
||||
|
||||
interface WorkflowTableRow {
|
||||
@@ -49,7 +48,6 @@ interface TableCellProps {
|
||||
overlayRefs: React.MutableRefObject<Map<string, HTMLDivElement>>
|
||||
accessiblePrefixes: ReturnType<typeof useAccessibleReferencePrefixes>
|
||||
workspaceId: string
|
||||
activeSearchTarget?: ActiveSearchTarget | null
|
||||
subBlockId: string
|
||||
}
|
||||
|
||||
@@ -68,9 +66,9 @@ function TableCell({
|
||||
overlayRefs,
|
||||
accessiblePrefixes,
|
||||
workspaceId,
|
||||
activeSearchTarget,
|
||||
subBlockId,
|
||||
}: TableCellProps) {
|
||||
const activeSearchTarget = useActiveSearchTarget()
|
||||
// Defensive programming: ensure row.cells exists and has the expected structure
|
||||
const hasValidCells = row.cells && typeof row.cells === 'object'
|
||||
if (!hasValidCells) logger.warn('Table row has malformed cells data:', row)
|
||||
@@ -217,8 +215,8 @@ export function Table({
|
||||
isPreview = false,
|
||||
previewValue,
|
||||
disabled = false,
|
||||
activeSearchTarget,
|
||||
}: TableProps) {
|
||||
const activeSearchTarget = useActiveSearchTarget()
|
||||
const params = useParams()
|
||||
const workspaceId = params.workspaceId as string
|
||||
const [storeValue, setStoreValue] = useSubBlockValue<WorkflowTableRow[]>(blockId, subBlockId)
|
||||
@@ -399,7 +397,6 @@ export function Table({
|
||||
overlayRefs={overlayRefs}
|
||||
accessiblePrefixes={accessiblePrefixes}
|
||||
workspaceId={workspaceId}
|
||||
activeSearchTarget={activeSearchTarget}
|
||||
subBlockId={subBlockId}
|
||||
/>
|
||||
))}
|
||||
|
||||
+2
-3
@@ -4,7 +4,7 @@ import { TimePicker } from '@/components/emcn'
|
||||
import { formatDisplayText } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/formatted-text'
|
||||
import { getWorkflowSearchLabelHighlight } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/workflow-search-highlight'
|
||||
import { useSubBlockValue } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/hooks/use-sub-block-value'
|
||||
import type { ActiveSearchTarget } from '@/stores/panel/editor/store'
|
||||
import { useActiveSearchTarget } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/providers/active-search-target-provider'
|
||||
|
||||
interface TimeInputProps {
|
||||
blockId: string
|
||||
@@ -14,7 +14,6 @@ interface TimeInputProps {
|
||||
previewValue?: string | null
|
||||
className?: string
|
||||
disabled?: boolean
|
||||
activeSearchTarget?: ActiveSearchTarget | null
|
||||
}
|
||||
|
||||
function formatTimeInputDisplayLabel(value: string): string {
|
||||
@@ -38,8 +37,8 @@ export function TimeInput({
|
||||
previewValue,
|
||||
className,
|
||||
disabled = false,
|
||||
activeSearchTarget,
|
||||
}: TimeInputProps) {
|
||||
const activeSearchTarget = useActiveSearchTarget()
|
||||
const [storeValue, setStoreValue] = useSubBlockValue<string>(blockId, subBlockId)
|
||||
|
||||
const value = isPreview ? previewValue : storeValue
|
||||
|
||||
+2
-3
@@ -18,11 +18,11 @@ import { getMissingRequiredScopes } from '@/lib/oauth/utils'
|
||||
import { ConnectOAuthModal } from '@/app/workspace/[workspaceId]/components/connect-oauth-modal'
|
||||
import { formatDisplayText } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/formatted-text'
|
||||
import { getWorkflowSearchLabelHighlight } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/workflow-search-highlight'
|
||||
import { useActiveSearchTarget } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/providers/active-search-target-provider'
|
||||
import { useWorkspaceCredential } from '@/hooks/queries/credentials'
|
||||
import { useOAuthCredentials } from '@/hooks/queries/oauth/oauth-credentials'
|
||||
import { useWorkflowMap } from '@/hooks/queries/workflows'
|
||||
import { useCredentialRefreshTriggers } from '@/hooks/use-credential-refresh-triggers'
|
||||
import type { ActiveSearchTarget } from '@/stores/panel/editor/store'
|
||||
import { useWorkflowRegistry } from '@/stores/workflows/registry/store'
|
||||
|
||||
const getProviderIcon = (providerName: OAuthProvider) => {
|
||||
@@ -64,7 +64,6 @@ interface ToolCredentialSelectorProps {
|
||||
label?: string
|
||||
serviceId: OAuthService
|
||||
disabled?: boolean
|
||||
activeSearchTarget?: ActiveSearchTarget | null
|
||||
}
|
||||
|
||||
const EMPTY_SCOPES: string[] = []
|
||||
@@ -79,8 +78,8 @@ export function ToolCredentialSelector({
|
||||
label,
|
||||
serviceId,
|
||||
disabled = false,
|
||||
activeSearchTarget,
|
||||
}: ToolCredentialSelectorProps) {
|
||||
const activeSearchTarget = useActiveSearchTarget()
|
||||
const params = useParams()
|
||||
const workspaceId = (params?.workspaceId as string) || ''
|
||||
const onChangeRef = useRef(onChange)
|
||||
|
||||
-4
@@ -8,7 +8,6 @@ import {
|
||||
import { parseStoredToolInputValue } from '@/lib/workflows/tool-input/types'
|
||||
import { SubBlock } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/sub-block'
|
||||
import type { SubBlockConfig as BlockSubBlockConfig } from '@/blocks/types'
|
||||
import type { ActiveSearchTarget } from '@/stores/panel/editor/store'
|
||||
import { useWorkflowRegistry } from '@/stores/workflows/registry/store'
|
||||
import { useSubBlockStore } from '@/stores/workflows/subblock/store'
|
||||
|
||||
@@ -26,7 +25,6 @@ interface ToolSubBlockRendererProps {
|
||||
disabled?: boolean
|
||||
onToggle?: () => void
|
||||
}
|
||||
activeSearchTarget?: ActiveSearchTarget | null
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -50,7 +48,6 @@ export function ToolSubBlockRenderer({
|
||||
onParamChange,
|
||||
disabled,
|
||||
canonicalToggle,
|
||||
activeSearchTarget,
|
||||
}: ToolSubBlockRendererProps) {
|
||||
const syntheticId = buildToolSubBlockId(subBlockId, toolIndex, effectiveParamId)
|
||||
const toolParamValue = toolParams?.[effectiveParamId] ?? ''
|
||||
@@ -128,7 +125,6 @@ export function ToolSubBlockRenderer({
|
||||
disabled={disabled}
|
||||
canonicalToggle={canonicalToggle}
|
||||
dependencyContext={toolParams}
|
||||
activeSearchTarget={activeSearchTarget}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
+183
-174
@@ -54,6 +54,10 @@ import {
|
||||
} from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/workflow-search-highlight'
|
||||
import { useSubBlockValue } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/hooks/use-sub-block-value'
|
||||
import type { WandControlHandlers } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/sub-block'
|
||||
import {
|
||||
ActiveSearchTargetProvider,
|
||||
useActiveSearchTarget,
|
||||
} from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/providers/active-search-target-provider'
|
||||
import { getAllBlocks } from '@/blocks'
|
||||
import type { SubBlockConfig as BlockSubBlockConfig } from '@/blocks/types'
|
||||
import { BUILT_IN_TOOL_TYPES } from '@/blocks/utils'
|
||||
@@ -137,7 +141,6 @@ function WorkflowInputMapperInput({
|
||||
onChange,
|
||||
disabled,
|
||||
workflowId,
|
||||
activeSearchTarget,
|
||||
}: {
|
||||
blockId: string
|
||||
paramId: string
|
||||
@@ -145,8 +148,8 @@ function WorkflowInputMapperInput({
|
||||
onChange: (value: string) => void
|
||||
disabled: boolean
|
||||
workflowId: string
|
||||
activeSearchTarget?: ActiveSearchTarget | null
|
||||
}) {
|
||||
const activeSearchTarget = useActiveSearchTarget()
|
||||
const { data: workflowState, isLoading } = useWorkflowState(workflowId)
|
||||
const inputFields = useMemo(
|
||||
() => (workflowState?.blocks ? extractInputFieldsFromBlocks(workflowState.blocks) : []),
|
||||
@@ -207,21 +210,21 @@ function WorkflowInputMapperInput({
|
||||
}
|
||||
: null
|
||||
return (
|
||||
<ShortInput
|
||||
key={field.name}
|
||||
blockId={blockId}
|
||||
subBlockId={syntheticId}
|
||||
placeholder={`Enter ${field.name}${field.type !== 'string' ? ` (${field.type})` : ''}`}
|
||||
value={String(parsedValue[field.name] ?? '')}
|
||||
onChange={(newValue: string) => handleFieldChange(field.name, newValue)}
|
||||
disabled={disabled}
|
||||
activeSearchTarget={fieldActiveSearchTarget}
|
||||
config={{
|
||||
id: syntheticId,
|
||||
type: 'short-input',
|
||||
title: field.name,
|
||||
}}
|
||||
/>
|
||||
<ActiveSearchTargetProvider key={field.name} value={fieldActiveSearchTarget}>
|
||||
<ShortInput
|
||||
blockId={blockId}
|
||||
subBlockId={syntheticId}
|
||||
placeholder={`Enter ${field.name}${field.type !== 'string' ? ` (${field.type})` : ''}`}
|
||||
value={String(parsedValue[field.name] ?? '')}
|
||||
onChange={(newValue: string) => handleFieldChange(field.name, newValue)}
|
||||
disabled={disabled}
|
||||
config={{
|
||||
id: syntheticId,
|
||||
type: 'short-input',
|
||||
title: field.name,
|
||||
}}
|
||||
/>
|
||||
</ActiveSearchTargetProvider>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
@@ -311,7 +314,6 @@ interface ToolInputProps {
|
||||
disabled?: boolean
|
||||
/** Allow expanding tools in preview mode */
|
||||
allowExpandInPreview?: boolean
|
||||
activeSearchTarget?: ActiveSearchTarget | null
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -459,11 +461,11 @@ export const ToolInput = memo(function ToolInput({
|
||||
previewValue,
|
||||
disabled = false,
|
||||
allowExpandInPreview,
|
||||
activeSearchTarget,
|
||||
}: ToolInputProps) {
|
||||
const params = useParams()
|
||||
const workspaceId = params.workspaceId as string
|
||||
const workflowId = params.workflowId as string
|
||||
const activeSearchTarget = useActiveSearchTarget()
|
||||
const [storeValue, setStoreValue] = useSubBlockValue(blockId, subBlockId)
|
||||
const [open, setOpen] = useState(false)
|
||||
const [customToolModalOpen, setCustomToolModalOpen] = useState(false)
|
||||
@@ -1169,149 +1171,16 @@ export const ToolInput = memo(function ToolInput({
|
||||
)
|
||||
const uiComponent = param.uiComponent
|
||||
|
||||
if (!uiComponent) {
|
||||
return (
|
||||
<ShortInput
|
||||
blockId={blockId}
|
||||
subBlockId={uniqueSubBlockId}
|
||||
placeholder={param.description || `Enter ${formatParameterLabel(param.id).toLowerCase()}`}
|
||||
password={isPasswordParameter(param.id)}
|
||||
config={{
|
||||
id: uniqueSubBlockId,
|
||||
type: 'short-input',
|
||||
title: param.id,
|
||||
}}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
wandControlRef={wandControlRef}
|
||||
hideInternalWand={true}
|
||||
activeSearchTarget={paramActiveSearchTarget}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
switch (uiComponent.type) {
|
||||
case 'dropdown': {
|
||||
const options =
|
||||
(uiComponent.options as { id?: string; label: string; value?: string }[] | undefined)
|
||||
?.filter((option) => (option.id ?? option.value) !== '')
|
||||
.map((option) => ({
|
||||
label: option.label,
|
||||
value: option.id ?? option.value ?? '',
|
||||
})) || []
|
||||
const selectedLabel = options.find((option) => option.value === value)?.label ?? ''
|
||||
const workflowSearchHighlight = getWorkflowSearchLabelHighlight({
|
||||
activeSearchTarget: paramActiveSearchTarget,
|
||||
blockId,
|
||||
subBlockId: uniqueSubBlockId,
|
||||
valuePath: [],
|
||||
label: selectedLabel,
|
||||
})
|
||||
const content = (() => {
|
||||
if (!uiComponent) {
|
||||
return (
|
||||
<Combobox
|
||||
options={options}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
placeholder={uiComponent.placeholder || 'Select option'}
|
||||
disabled={disabled}
|
||||
overlayContent={
|
||||
workflowSearchHighlight ? (
|
||||
<span className='truncate text-[var(--text-primary)]'>
|
||||
{formatDisplayText(selectedLabel, { workflowSearchHighlight })}
|
||||
</span>
|
||||
) : undefined
|
||||
<ShortInput
|
||||
blockId={blockId}
|
||||
subBlockId={uniqueSubBlockId}
|
||||
placeholder={
|
||||
param.description || `Enter ${formatParameterLabel(param.id).toLowerCase()}`
|
||||
}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
case 'switch':
|
||||
return (
|
||||
<Switch
|
||||
checked={value === 'true' || value === 'True'}
|
||||
onCheckedChange={(checked) => onChange(checked ? 'true' : 'false')}
|
||||
/>
|
||||
)
|
||||
|
||||
case 'long-input':
|
||||
return (
|
||||
<LongInput
|
||||
blockId={blockId}
|
||||
subBlockId={uniqueSubBlockId}
|
||||
placeholder={uiComponent.placeholder || param.description}
|
||||
config={{
|
||||
id: uniqueSubBlockId,
|
||||
type: 'long-input',
|
||||
title: param.id,
|
||||
wandConfig: uiComponent.wandConfig,
|
||||
}}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
wandControlRef={wandControlRef}
|
||||
hideInternalWand={true}
|
||||
activeSearchTarget={paramActiveSearchTarget}
|
||||
/>
|
||||
)
|
||||
|
||||
case 'short-input':
|
||||
return (
|
||||
<ShortInput
|
||||
blockId={blockId}
|
||||
subBlockId={uniqueSubBlockId}
|
||||
placeholder={uiComponent.placeholder || param.description}
|
||||
password={uiComponent.password || isPasswordParameter(param.id)}
|
||||
config={{
|
||||
id: uniqueSubBlockId,
|
||||
type: 'short-input',
|
||||
title: param.id,
|
||||
wandConfig: uiComponent.wandConfig,
|
||||
}}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
disabled={disabled}
|
||||
wandControlRef={wandControlRef}
|
||||
hideInternalWand={true}
|
||||
activeSearchTarget={paramActiveSearchTarget}
|
||||
/>
|
||||
)
|
||||
|
||||
case 'oauth-input':
|
||||
return (
|
||||
<ToolCredentialSelector
|
||||
blockId={blockId}
|
||||
subBlockId={uniqueSubBlockId}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
provider={getProviderIdFromServiceId(uiComponent.serviceId || '') as OAuthProvider}
|
||||
serviceId={uiComponent.serviceId as OAuthService}
|
||||
disabled={disabled}
|
||||
requiredScopes={uiComponent.requiredScopes || []}
|
||||
activeSearchTarget={paramActiveSearchTarget}
|
||||
/>
|
||||
)
|
||||
|
||||
case 'workflow-input-mapper': {
|
||||
const selectedWorkflowId = currentToolParams?.workflowId || ''
|
||||
return (
|
||||
<WorkflowInputMapperInput
|
||||
blockId={blockId}
|
||||
paramId={param.id}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
disabled={disabled}
|
||||
workflowId={selectedWorkflowId}
|
||||
activeSearchTarget={paramActiveSearchTarget}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
default:
|
||||
return (
|
||||
<ShortInput
|
||||
blockId={blockId}
|
||||
subBlockId={uniqueSubBlockId}
|
||||
placeholder={uiComponent.placeholder || param.description}
|
||||
password={uiComponent.password || isPasswordParameter(param.id)}
|
||||
password={isPasswordParameter(param.id)}
|
||||
config={{
|
||||
id: uniqueSubBlockId,
|
||||
type: 'short-input',
|
||||
@@ -1321,10 +1190,147 @@ export const ToolInput = memo(function ToolInput({
|
||||
onChange={onChange}
|
||||
wandControlRef={wandControlRef}
|
||||
hideInternalWand={true}
|
||||
activeSearchTarget={paramActiveSearchTarget}
|
||||
/>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
switch (uiComponent.type) {
|
||||
case 'dropdown': {
|
||||
const options =
|
||||
(uiComponent.options as { id?: string; label: string; value?: string }[] | undefined)
|
||||
?.filter((option) => (option.id ?? option.value) !== '')
|
||||
.map((option) => ({
|
||||
label: option.label,
|
||||
value: option.id ?? option.value ?? '',
|
||||
})) || []
|
||||
const selectedLabel = options.find((option) => option.value === value)?.label ?? ''
|
||||
const workflowSearchHighlight = getWorkflowSearchLabelHighlight({
|
||||
activeSearchTarget: paramActiveSearchTarget,
|
||||
blockId,
|
||||
subBlockId: uniqueSubBlockId,
|
||||
valuePath: [],
|
||||
label: selectedLabel,
|
||||
})
|
||||
return (
|
||||
<Combobox
|
||||
options={options}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
placeholder={uiComponent.placeholder || 'Select option'}
|
||||
disabled={disabled}
|
||||
overlayContent={
|
||||
workflowSearchHighlight ? (
|
||||
<span className='truncate text-[var(--text-primary)]'>
|
||||
{formatDisplayText(selectedLabel, { workflowSearchHighlight })}
|
||||
</span>
|
||||
) : undefined
|
||||
}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
case 'switch':
|
||||
return (
|
||||
<Switch
|
||||
checked={value === 'true' || value === 'True'}
|
||||
onCheckedChange={(checked) => onChange(checked ? 'true' : 'false')}
|
||||
/>
|
||||
)
|
||||
|
||||
case 'long-input':
|
||||
return (
|
||||
<LongInput
|
||||
blockId={blockId}
|
||||
subBlockId={uniqueSubBlockId}
|
||||
placeholder={uiComponent.placeholder || param.description}
|
||||
config={{
|
||||
id: uniqueSubBlockId,
|
||||
type: 'long-input',
|
||||
title: param.id,
|
||||
wandConfig: uiComponent.wandConfig,
|
||||
}}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
wandControlRef={wandControlRef}
|
||||
hideInternalWand={true}
|
||||
/>
|
||||
)
|
||||
|
||||
case 'short-input':
|
||||
return (
|
||||
<ShortInput
|
||||
blockId={blockId}
|
||||
subBlockId={uniqueSubBlockId}
|
||||
placeholder={uiComponent.placeholder || param.description}
|
||||
password={uiComponent.password || isPasswordParameter(param.id)}
|
||||
config={{
|
||||
id: uniqueSubBlockId,
|
||||
type: 'short-input',
|
||||
title: param.id,
|
||||
wandConfig: uiComponent.wandConfig,
|
||||
}}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
disabled={disabled}
|
||||
wandControlRef={wandControlRef}
|
||||
hideInternalWand={true}
|
||||
/>
|
||||
)
|
||||
|
||||
case 'oauth-input':
|
||||
return (
|
||||
<ToolCredentialSelector
|
||||
blockId={blockId}
|
||||
subBlockId={uniqueSubBlockId}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
provider={getProviderIdFromServiceId(uiComponent.serviceId || '') as OAuthProvider}
|
||||
serviceId={uiComponent.serviceId as OAuthService}
|
||||
disabled={disabled}
|
||||
requiredScopes={uiComponent.requiredScopes || []}
|
||||
/>
|
||||
)
|
||||
|
||||
case 'workflow-input-mapper': {
|
||||
const selectedWorkflowId = currentToolParams?.workflowId || ''
|
||||
return (
|
||||
<WorkflowInputMapperInput
|
||||
blockId={blockId}
|
||||
paramId={param.id}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
disabled={disabled}
|
||||
workflowId={selectedWorkflowId}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
default:
|
||||
return (
|
||||
<ShortInput
|
||||
blockId={blockId}
|
||||
subBlockId={uniqueSubBlockId}
|
||||
placeholder={uiComponent.placeholder || param.description}
|
||||
password={uiComponent.password || isPasswordParameter(param.id)}
|
||||
config={{
|
||||
id: uniqueSubBlockId,
|
||||
type: 'short-input',
|
||||
title: param.id,
|
||||
}}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
wandControlRef={wandControlRef}
|
||||
hideInternalWand={true}
|
||||
/>
|
||||
)
|
||||
}
|
||||
})()
|
||||
|
||||
return (
|
||||
<ActiveSearchTargetProvider value={paramActiveSearchTarget}>
|
||||
{content}
|
||||
</ActiveSearchTargetProvider>
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2047,23 +2053,26 @@ export const ToolInput = memo(function ToolInput({
|
||||
: { ...sb, title: formatParameterLabel(effectiveParamId) }
|
||||
|
||||
return (
|
||||
<ToolSubBlockRenderer
|
||||
<ActiveSearchTargetProvider
|
||||
key={sb.id}
|
||||
blockId={blockId}
|
||||
subBlockId={subBlockId}
|
||||
toolIndex={toolIndex}
|
||||
subBlock={sbWithTitle}
|
||||
effectiveParamId={effectiveParamId}
|
||||
toolParams={tool.params}
|
||||
onParamChange={handleParamChange}
|
||||
disabled={disabled}
|
||||
canonicalToggle={canonicalToggleProp}
|
||||
activeSearchTarget={getParamActiveSearchTarget(
|
||||
value={getParamActiveSearchTarget(
|
||||
toolIndex,
|
||||
effectiveParamId,
|
||||
buildToolSubBlockId(subBlockId, toolIndex, effectiveParamId)
|
||||
)}
|
||||
/>
|
||||
>
|
||||
<ToolSubBlockRenderer
|
||||
blockId={blockId}
|
||||
subBlockId={subBlockId}
|
||||
toolIndex={toolIndex}
|
||||
subBlock={sbWithTitle}
|
||||
effectiveParamId={effectiveParamId}
|
||||
toolParams={tool.params}
|
||||
onParamChange={handleParamChange}
|
||||
disabled={disabled}
|
||||
canonicalToggle={canonicalToggleProp}
|
||||
/>
|
||||
</ActiveSearchTargetProvider>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
+2
-3
@@ -24,8 +24,8 @@ import {
|
||||
getWorkflowSearchLabelHighlight,
|
||||
} from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/workflow-search-highlight'
|
||||
import { useSubBlockValue } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/hooks/use-sub-block-value'
|
||||
import { useActiveSearchTarget } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/providers/active-search-target-provider'
|
||||
import { useAccessibleReferencePrefixes } from '@/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-accessible-reference-prefixes'
|
||||
import type { ActiveSearchTarget } from '@/stores/panel/editor/store'
|
||||
import { useVariablesStore } from '@/stores/variables/store'
|
||||
import type { Variable } from '@/stores/variables/types'
|
||||
import { useWorkflowRegistry } from '@/stores/workflows/registry/store'
|
||||
@@ -45,7 +45,6 @@ interface VariablesInputProps {
|
||||
isPreview?: boolean
|
||||
previewValue?: VariableAssignment[] | null
|
||||
disabled?: boolean
|
||||
activeSearchTarget?: ActiveSearchTarget | null
|
||||
}
|
||||
|
||||
const DEFAULT_ASSIGNMENT: Omit<VariableAssignment, 'id'> = {
|
||||
@@ -90,8 +89,8 @@ export function VariablesInput({
|
||||
isPreview = false,
|
||||
previewValue,
|
||||
disabled = false,
|
||||
activeSearchTarget,
|
||||
}: VariablesInputProps) {
|
||||
const activeSearchTarget = useActiveSearchTarget()
|
||||
const params = useParams()
|
||||
const workflowId = params.workflowId as string
|
||||
const [storeValue, setStoreValue] = useSubBlockValue<VariableAssignment[]>(blockId, subBlockId)
|
||||
|
||||
-4
@@ -6,7 +6,6 @@ import { DELETED_WORKFLOW_LABEL } from '@/app/workspace/[workspaceId]/logs/utils
|
||||
import { SelectorCombobox } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/selector-combobox/selector-combobox'
|
||||
import type { SubBlockConfig } from '@/blocks/types'
|
||||
import type { SelectorContext } from '@/hooks/selectors/types'
|
||||
import type { ActiveSearchTarget } from '@/stores/panel/editor/store'
|
||||
import { useWorkflowRegistry } from '@/stores/workflows/registry/store'
|
||||
|
||||
interface WorkflowSelectorInputProps {
|
||||
@@ -15,7 +14,6 @@ interface WorkflowSelectorInputProps {
|
||||
disabled?: boolean
|
||||
isPreview?: boolean
|
||||
previewValue?: string | null
|
||||
activeSearchTarget?: ActiveSearchTarget | null
|
||||
}
|
||||
|
||||
export function WorkflowSelectorInput({
|
||||
@@ -24,7 +22,6 @@ export function WorkflowSelectorInput({
|
||||
disabled = false,
|
||||
isPreview = false,
|
||||
previewValue,
|
||||
activeSearchTarget,
|
||||
}: WorkflowSelectorInputProps) {
|
||||
const { workspaceId } = useParams<{ workspaceId: string }>()
|
||||
const activeWorkflowId = useWorkflowRegistry((s) => s.activeWorkflowId)
|
||||
@@ -48,7 +45,6 @@ export function WorkflowSelectorInput({
|
||||
previewValue={previewValue}
|
||||
placeholder={subBlock.placeholder || 'Select workflow...'}
|
||||
missingOptionLabel={DELETED_WORKFLOW_LABEL}
|
||||
activeSearchTarget={activeSearchTarget}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
+1
-4
@@ -38,8 +38,6 @@ export interface UseSubBlockInputOptions {
|
||||
onStreamingEnd?: () => void
|
||||
/** Optional preview value for read-only preview displays. */
|
||||
previewValue?: string | null
|
||||
/** Optional workspace id; if omitted, derived from route params. */
|
||||
workspaceId?: string
|
||||
/**
|
||||
* Optional callback to force/show the env var dropdown (e.g., API key fields).
|
||||
* Return { show: true, searchTerm?: string } to override defaults.
|
||||
@@ -162,13 +160,12 @@ export function useSubBlockInput(options: UseSubBlockInputOptions): UseSubBlockI
|
||||
isStreaming = false,
|
||||
onStreamingEnd,
|
||||
previewValue,
|
||||
workspaceId: workspaceIdProp,
|
||||
shouldForceEnvDropdown,
|
||||
shouldForceTagDropdown,
|
||||
} = options
|
||||
|
||||
const params = useParams()
|
||||
const workspaceId = (workspaceIdProp || (params?.workspaceId as string)) ?? undefined
|
||||
const workspaceId = (params?.workspaceId as string) ?? undefined
|
||||
|
||||
const [storeValue, setStoreValue] = useSubBlockValue(blockId, subBlockId, false, {
|
||||
isStreaming,
|
||||
|
||||
-40
@@ -54,7 +54,6 @@ import { MODAL_REGISTRY } from '@/app/workspace/[workspaceId]/w/[workflowId]/com
|
||||
import { useDependsOnGate } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/hooks/use-depends-on-gate'
|
||||
import type { SubBlockConfig } from '@/blocks/types'
|
||||
import { useWebhookManagement } from '@/hooks/use-webhook-management'
|
||||
import type { ActiveSearchTarget } from '@/stores/panel/editor/store'
|
||||
|
||||
const SLACK_OVERRIDES: SelectorOverrides = {
|
||||
transformContext: (context, deps) => {
|
||||
@@ -105,7 +104,6 @@ interface SubBlockProps {
|
||||
/** Provides sibling values for dependency resolution in non-preview contexts (e.g. tool-input) */
|
||||
dependencyContext?: Record<string, unknown>
|
||||
isSearchHighlighted?: boolean
|
||||
activeSearchTarget?: ActiveSearchTarget | null
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -441,7 +439,6 @@ const arePropsEqual = (prevProps: SubBlockProps, nextProps: SubBlockProps): bool
|
||||
canonicalToggleEqual &&
|
||||
prevProps.labelSuffix === nextProps.labelSuffix &&
|
||||
prevProps.isSearchHighlighted === nextProps.isSearchHighlighted &&
|
||||
prevProps.activeSearchTarget === nextProps.activeSearchTarget &&
|
||||
prevProps.dependencyContext === nextProps.dependencyContext
|
||||
)
|
||||
}
|
||||
@@ -459,7 +456,6 @@ const arePropsEqual = (prevProps: SubBlockProps, nextProps: SubBlockProps): bool
|
||||
* @param labelSuffix - Additional content rendered after the label text
|
||||
* @param dependencyContext - Sibling values for dependency resolution in non-preview contexts (e.g. tool-input)
|
||||
* @param isSearchHighlighted - Whether workflow search should highlight this field
|
||||
* @param activeSearchTarget - Active workflow search target for nested field highlighting
|
||||
*/
|
||||
function SubBlockComponent({
|
||||
blockId,
|
||||
@@ -472,7 +468,6 @@ function SubBlockComponent({
|
||||
labelSuffix,
|
||||
dependencyContext,
|
||||
isSearchHighlighted,
|
||||
activeSearchTarget,
|
||||
}: SubBlockProps): JSX.Element {
|
||||
const params = useParams()
|
||||
const workspaceId = params.workspaceId as string
|
||||
@@ -655,7 +650,6 @@ function SubBlockComponent({
|
||||
wandControlRef={wandControlRef}
|
||||
hideInternalWand={true}
|
||||
isSearchHighlighted={isSearchHighlighted}
|
||||
activeSearchTarget={activeSearchTarget}
|
||||
/>
|
||||
)
|
||||
|
||||
@@ -672,7 +666,6 @@ function SubBlockComponent({
|
||||
disabled={isDisabled}
|
||||
wandControlRef={wandControlRef}
|
||||
hideInternalWand={true}
|
||||
activeSearchTarget={activeSearchTarget}
|
||||
/>
|
||||
)
|
||||
|
||||
@@ -693,7 +686,6 @@ function SubBlockComponent({
|
||||
fetchOptionById={config.fetchOptionById}
|
||||
dependsOn={config.dependsOn}
|
||||
searchable={config.searchable}
|
||||
activeSearchTarget={activeSearchTarget}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
@@ -707,7 +699,6 @@ function SubBlockComponent({
|
||||
disabled={isDisabled}
|
||||
isPreview={isPreview}
|
||||
previewValue={previewValue as string | null}
|
||||
activeSearchTarget={activeSearchTarget}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
@@ -728,7 +719,6 @@ function SubBlockComponent({
|
||||
fetchOptions={config.fetchOptions}
|
||||
fetchOptionById={config.fetchOptionById}
|
||||
dependsOn={config.dependsOn}
|
||||
activeSearchTarget={activeSearchTarget}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
@@ -758,7 +748,6 @@ function SubBlockComponent({
|
||||
isPreview={isPreview}
|
||||
previewValue={previewValue as any}
|
||||
disabled={isDisabled}
|
||||
activeSearchTarget={activeSearchTarget}
|
||||
/>
|
||||
)
|
||||
|
||||
@@ -791,7 +780,6 @@ function SubBlockComponent({
|
||||
}
|
||||
wandControlRef={wandControlRef}
|
||||
hideInternalWand={true}
|
||||
activeSearchTarget={activeSearchTarget}
|
||||
/>
|
||||
)
|
||||
|
||||
@@ -816,7 +804,6 @@ function SubBlockComponent({
|
||||
previewValue={previewValue}
|
||||
disabled={allowExpandInPreview ? false : isDisabled}
|
||||
allowExpandInPreview={allowExpandInPreview}
|
||||
activeSearchTarget={activeSearchTarget}
|
||||
/>
|
||||
)
|
||||
|
||||
@@ -828,7 +815,6 @@ function SubBlockComponent({
|
||||
isPreview={isPreview}
|
||||
previewValue={previewValue}
|
||||
disabled={isDisabled}
|
||||
activeSearchTarget={activeSearchTarget}
|
||||
/>
|
||||
)
|
||||
|
||||
@@ -848,7 +834,6 @@ function SubBlockComponent({
|
||||
subBlockValues={subBlockValues}
|
||||
disabled={isDisabled}
|
||||
subBlockId={config.id}
|
||||
activeSearchTarget={activeSearchTarget}
|
||||
/>
|
||||
)
|
||||
|
||||
@@ -863,7 +848,6 @@ function SubBlockComponent({
|
||||
subBlockValues={subBlockValues ?? {}}
|
||||
disabled={isDisabled}
|
||||
maxHeight={config.maxHeight}
|
||||
activeSearchTarget={activeSearchTarget}
|
||||
/>
|
||||
)
|
||||
|
||||
@@ -875,7 +859,6 @@ function SubBlockComponent({
|
||||
isPreview={isPreview}
|
||||
previewValue={previewValue as any}
|
||||
disabled={isDisabled}
|
||||
activeSearchTarget={activeSearchTarget}
|
||||
/>
|
||||
)
|
||||
|
||||
@@ -888,7 +871,6 @@ function SubBlockComponent({
|
||||
previewValue={previewValue as any}
|
||||
disabled={isDisabled}
|
||||
mode='router'
|
||||
activeSearchTarget={activeSearchTarget}
|
||||
/>
|
||||
)
|
||||
|
||||
@@ -900,7 +882,6 @@ function SubBlockComponent({
|
||||
isPreview={isPreview}
|
||||
previewValue={previewValue as any}
|
||||
disabled={isDisabled}
|
||||
activeSearchTarget={activeSearchTarget}
|
||||
/>
|
||||
)
|
||||
|
||||
@@ -913,7 +894,6 @@ function SubBlockComponent({
|
||||
isPreview={isPreview}
|
||||
previewValue={previewValue as any}
|
||||
disabled={isDisabled}
|
||||
activeSearchTarget={activeSearchTarget}
|
||||
/>
|
||||
)
|
||||
|
||||
@@ -928,7 +908,6 @@ function SubBlockComponent({
|
||||
isPreview={isPreview}
|
||||
previewValue={previewValue as any}
|
||||
disabled={isDisabled}
|
||||
activeSearchTarget={activeSearchTarget}
|
||||
/>
|
||||
)
|
||||
|
||||
@@ -944,7 +923,6 @@ function SubBlockComponent({
|
||||
isPreview={isPreview}
|
||||
previewValue={previewValue}
|
||||
previewContextValues={contextValues}
|
||||
activeSearchTarget={activeSearchTarget}
|
||||
/>
|
||||
)
|
||||
|
||||
@@ -959,7 +937,6 @@ function SubBlockComponent({
|
||||
isPreview={isPreview}
|
||||
previewValue={previewValue}
|
||||
previewContextValues={contextValues}
|
||||
activeSearchTarget={activeSearchTarget}
|
||||
/>
|
||||
)
|
||||
|
||||
@@ -973,7 +950,6 @@ function SubBlockComponent({
|
||||
previewValue={previewValue}
|
||||
previewContextValues={contextValues}
|
||||
overrides={FOLDER_OVERRIDES}
|
||||
activeSearchTarget={activeSearchTarget}
|
||||
/>
|
||||
)
|
||||
|
||||
@@ -985,7 +961,6 @@ function SubBlockComponent({
|
||||
disabled={isDisabled}
|
||||
isPreview={isPreview}
|
||||
previewValue={previewValue as any}
|
||||
activeSearchTarget={activeSearchTarget}
|
||||
/>
|
||||
)
|
||||
|
||||
@@ -998,7 +973,6 @@ function SubBlockComponent({
|
||||
isPreview={isPreview}
|
||||
previewValue={previewValue as any}
|
||||
previewContextValues={contextValues}
|
||||
activeSearchTarget={activeSearchTarget}
|
||||
/>
|
||||
)
|
||||
|
||||
@@ -1011,7 +985,6 @@ function SubBlockComponent({
|
||||
isPreview={isPreview}
|
||||
previewValue={previewValue as any}
|
||||
previewContextValues={contextValues}
|
||||
activeSearchTarget={activeSearchTarget}
|
||||
/>
|
||||
)
|
||||
|
||||
@@ -1024,7 +997,6 @@ function SubBlockComponent({
|
||||
isPreview={isPreview}
|
||||
previewValue={previewValue}
|
||||
previewContextValues={contextValues}
|
||||
activeSearchTarget={activeSearchTarget}
|
||||
/>
|
||||
)
|
||||
|
||||
@@ -1038,7 +1010,6 @@ function SubBlockComponent({
|
||||
disabled={isDisabled}
|
||||
config={config}
|
||||
showValue={true}
|
||||
activeSearchTarget={activeSearchTarget}
|
||||
/>
|
||||
)
|
||||
|
||||
@@ -1051,7 +1022,6 @@ function SubBlockComponent({
|
||||
previewValue={previewValue as any}
|
||||
disabled={isDisabled}
|
||||
previewContextValues={contextValues}
|
||||
activeSearchTarget={activeSearchTarget}
|
||||
/>
|
||||
)
|
||||
|
||||
@@ -1063,7 +1033,6 @@ function SubBlockComponent({
|
||||
isPreview={isPreview}
|
||||
previewValue={previewValue as any}
|
||||
disabled={isDisabled}
|
||||
activeSearchTarget={activeSearchTarget}
|
||||
/>
|
||||
)
|
||||
|
||||
@@ -1076,7 +1045,6 @@ function SubBlockComponent({
|
||||
previewValue={previewValue}
|
||||
config={config}
|
||||
disabled={isDisabled}
|
||||
activeSearchTarget={activeSearchTarget}
|
||||
/>
|
||||
)
|
||||
|
||||
@@ -1088,7 +1056,6 @@ function SubBlockComponent({
|
||||
isPreview={isPreview}
|
||||
previewValue={previewValue as FilterRule[] | null | undefined}
|
||||
disabled={isDisabled}
|
||||
activeSearchTarget={activeSearchTarget}
|
||||
/>
|
||||
)
|
||||
|
||||
@@ -1100,7 +1067,6 @@ function SubBlockComponent({
|
||||
isPreview={isPreview}
|
||||
previewValue={previewValue as SortRule[] | null | undefined}
|
||||
disabled={isDisabled}
|
||||
activeSearchTarget={activeSearchTarget}
|
||||
/>
|
||||
)
|
||||
|
||||
@@ -1115,7 +1081,6 @@ function SubBlockComponent({
|
||||
previewValue={previewValue}
|
||||
previewContextValues={contextValues}
|
||||
overrides={SLACK_OVERRIDES}
|
||||
activeSearchTarget={activeSearchTarget}
|
||||
/>
|
||||
)
|
||||
|
||||
@@ -1127,7 +1092,6 @@ function SubBlockComponent({
|
||||
disabled={isDisabled}
|
||||
isPreview={isPreview}
|
||||
previewValue={previewValue as string | null}
|
||||
activeSearchTarget={activeSearchTarget}
|
||||
/>
|
||||
)
|
||||
|
||||
@@ -1139,7 +1103,6 @@ function SubBlockComponent({
|
||||
disabled={isDisabled}
|
||||
isPreview={isPreview}
|
||||
previewValue={previewValue as any}
|
||||
activeSearchTarget={activeSearchTarget}
|
||||
/>
|
||||
)
|
||||
|
||||
@@ -1152,7 +1115,6 @@ function SubBlockComponent({
|
||||
isPreview={isPreview}
|
||||
previewValue={previewValue as any}
|
||||
previewContextValues={contextValues}
|
||||
activeSearchTarget={activeSearchTarget}
|
||||
/>
|
||||
)
|
||||
|
||||
@@ -1165,7 +1127,6 @@ function SubBlockComponent({
|
||||
isPreview={isPreview}
|
||||
previewValue={previewValue}
|
||||
previewContextValues={contextValues}
|
||||
activeSearchTarget={activeSearchTarget}
|
||||
/>
|
||||
)
|
||||
|
||||
@@ -1202,7 +1163,6 @@ function SubBlockComponent({
|
||||
previewValue={previewValue as any}
|
||||
disabled={isDisabled}
|
||||
wandControlRef={wandControlRef}
|
||||
activeSearchTarget={activeSearchTarget}
|
||||
/>
|
||||
)
|
||||
|
||||
|
||||
+2
-3
@@ -10,7 +10,7 @@ import {
|
||||
} from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/formatted-text'
|
||||
import { TagDropdown } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/tag-dropdown/tag-dropdown'
|
||||
import { getActiveWorkflowSearchHighlight } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/workflow-search-highlight'
|
||||
import type { ActiveSearchTarget } from '@/stores/panel/editor/store'
|
||||
import { useActiveSearchTarget } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/providers/active-search-target-provider'
|
||||
import type { BlockState } from '@/stores/workflows/workflow/types'
|
||||
import type { ConnectedBlock } from '../../hooks/use-block-connections'
|
||||
import { useSubflowEditor } from '../../hooks/use-subflow-editor'
|
||||
@@ -39,7 +39,6 @@ interface SubflowEditorProps {
|
||||
toggleConnectionsCollapsed: () => void
|
||||
userCanEdit: boolean
|
||||
isConnectionsAtMinHeight: boolean
|
||||
activeSearchTarget?: ActiveSearchTarget | null
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -60,8 +59,8 @@ export function SubflowEditor({
|
||||
toggleConnectionsCollapsed,
|
||||
userCanEdit,
|
||||
isConnectionsAtMinHeight,
|
||||
activeSearchTarget,
|
||||
}: SubflowEditorProps) {
|
||||
const activeSearchTarget = useActiveSearchTarget()
|
||||
const {
|
||||
subflowConfig,
|
||||
currentType,
|
||||
|
||||
+373
-371
@@ -39,6 +39,7 @@ import {
|
||||
useEditorBlockProperties,
|
||||
useEditorSubblockLayout,
|
||||
} from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/hooks'
|
||||
import { ActiveSearchTargetProvider } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/providers/active-search-target-provider'
|
||||
import { LoopTool } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/subflows/loop/loop-config'
|
||||
import { ParallelTool } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/subflows/parallel/parallel-config'
|
||||
import { getSubBlockStableKey } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/utils'
|
||||
@@ -368,110 +369,111 @@ export function Editor() {
|
||||
const isConnectionsAtMinHeight = connectionsHeight <= 35
|
||||
|
||||
return (
|
||||
<div className='flex h-full flex-col'>
|
||||
{/* Header */}
|
||||
<div className='mx-[-1px] flex flex-shrink-0 items-center justify-between rounded-none border border-[var(--border)] bg-[var(--surface-4)] px-3 py-1.5'>
|
||||
<div className='flex min-w-0 flex-1 items-center gap-2'>
|
||||
{(blockConfig || isSubflow) && currentBlock?.type !== 'note' && (
|
||||
<div
|
||||
className='flex size-[18px] items-center justify-center rounded-sm'
|
||||
style={{ background: isSubflow ? subflowConfig?.bgColor : blockConfig?.bgColor }}
|
||||
>
|
||||
<IconComponent
|
||||
icon={isSubflow ? subflowConfig?.icon : blockConfig?.icon}
|
||||
className='size-[12px] text-[var(--white)]'
|
||||
<ActiveSearchTargetProvider value={activeSearchTargetForCurrentBlock}>
|
||||
<div className='flex h-full flex-col'>
|
||||
{/* Header */}
|
||||
<div className='mx-[-1px] flex flex-shrink-0 items-center justify-between rounded-none border border-[var(--border)] bg-[var(--surface-4)] px-3 py-1.5'>
|
||||
<div className='flex min-w-0 flex-1 items-center gap-2'>
|
||||
{(blockConfig || isSubflow) && currentBlock?.type !== 'note' && (
|
||||
<div
|
||||
className='flex size-[18px] items-center justify-center rounded-sm'
|
||||
style={{ background: isSubflow ? subflowConfig?.bgColor : blockConfig?.bgColor }}
|
||||
>
|
||||
<IconComponent
|
||||
icon={isSubflow ? subflowConfig?.icon : blockConfig?.icon}
|
||||
className='size-[12px] text-[var(--white)]'
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{isRenaming ? (
|
||||
<input
|
||||
ref={nameInputRefCallback}
|
||||
type='text'
|
||||
value={editedName}
|
||||
onChange={(e) => setEditedName(e.target.value)}
|
||||
onBlur={handleSaveRename}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter') {
|
||||
handleSaveRename()
|
||||
} else if (e.key === 'Escape') {
|
||||
handleCancelRename()
|
||||
}
|
||||
}}
|
||||
className='min-w-0 flex-1 truncate bg-transparent pr-2 font-medium text-[var(--text-primary)] text-sm outline-none'
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{isRenaming ? (
|
||||
<input
|
||||
ref={nameInputRefCallback}
|
||||
type='text'
|
||||
value={editedName}
|
||||
onChange={(e) => setEditedName(e.target.value)}
|
||||
onBlur={handleSaveRename}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter') {
|
||||
handleSaveRename()
|
||||
} else if (e.key === 'Escape') {
|
||||
handleCancelRename()
|
||||
}
|
||||
}}
|
||||
className='min-w-0 flex-1 truncate bg-transparent pr-2 font-medium text-[var(--text-primary)] text-sm outline-none'
|
||||
/>
|
||||
) : (
|
||||
<h2
|
||||
className='min-w-0 flex-1 cursor-pointer select-none text-ellipsis whitespace-nowrap pr-2 font-medium text-[var(--text-primary)] text-sm [overflow-clip-margin:3px] [overflow:clip]'
|
||||
title={title}
|
||||
onDoubleClick={handleStartRename}
|
||||
onMouseDown={(e) => {
|
||||
if (e.detail === 2) {
|
||||
e.preventDefault()
|
||||
}
|
||||
}}
|
||||
>
|
||||
{blockNameSearchHighlight
|
||||
? formatDisplayText(title, { workflowSearchHighlight: blockNameSearchHighlight })
|
||||
: title}
|
||||
</h2>
|
||||
)}
|
||||
</div>
|
||||
<div className='flex shrink-0 items-center gap-2'>
|
||||
{/* Locked indicator - clickable to unlock if user has admin permissions, block is locked directly, and not locked by an ancestor */}
|
||||
{isLocked && currentBlock && (
|
||||
<Tooltip.Root>
|
||||
<Tooltip.Trigger asChild>
|
||||
{userPermissions.canAdmin && currentBlock.locked && !isAncestorLocked ? (
|
||||
) : (
|
||||
<h2
|
||||
className='min-w-0 flex-1 cursor-pointer select-none text-ellipsis whitespace-nowrap pr-2 font-medium text-[var(--text-primary)] text-sm [overflow-clip-margin:3px] [overflow:clip]'
|
||||
title={title}
|
||||
onDoubleClick={handleStartRename}
|
||||
onMouseDown={(e) => {
|
||||
if (e.detail === 2) {
|
||||
e.preventDefault()
|
||||
}
|
||||
}}
|
||||
>
|
||||
{blockNameSearchHighlight
|
||||
? formatDisplayText(title, { workflowSearchHighlight: blockNameSearchHighlight })
|
||||
: title}
|
||||
</h2>
|
||||
)}
|
||||
</div>
|
||||
<div className='flex shrink-0 items-center gap-2'>
|
||||
{/* Locked indicator - clickable to unlock if user has admin permissions, block is locked directly, and not locked by an ancestor */}
|
||||
{isLocked && currentBlock && (
|
||||
<Tooltip.Root>
|
||||
<Tooltip.Trigger asChild>
|
||||
{userPermissions.canAdmin && currentBlock.locked && !isAncestorLocked ? (
|
||||
<Button
|
||||
variant='ghost'
|
||||
className='p-0'
|
||||
onClick={() => collaborativeBatchToggleLocked([currentBlockId!])}
|
||||
aria-label='Unlock block'
|
||||
>
|
||||
<Unlock className='size-[14px] text-[var(--text-secondary)]' />
|
||||
</Button>
|
||||
) : (
|
||||
<div className='flex items-center justify-center'>
|
||||
<Lock className='size-[14px] text-[var(--text-secondary)]' />
|
||||
</div>
|
||||
)}
|
||||
</Tooltip.Trigger>
|
||||
<Tooltip.Content side='top'>
|
||||
<p>
|
||||
{isAncestorLocked
|
||||
? 'Ancestor container is locked'
|
||||
: userPermissions.canAdmin && currentBlock.locked
|
||||
? 'Unlock block'
|
||||
: 'Block is locked'}
|
||||
</p>
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Root>
|
||||
)}
|
||||
{/* Rename button */}
|
||||
{currentBlock && (
|
||||
<Tooltip.Root>
|
||||
<Tooltip.Trigger asChild>
|
||||
<Button
|
||||
variant='ghost'
|
||||
className='p-0'
|
||||
onClick={() => collaborativeBatchToggleLocked([currentBlockId!])}
|
||||
aria-label='Unlock block'
|
||||
onClick={isRenaming ? handleSaveRename : handleStartRename}
|
||||
disabled={!canEditBlock}
|
||||
aria-label={isRenaming ? 'Save name' : 'Rename block'}
|
||||
>
|
||||
<Unlock className='size-[14px] text-[var(--text-secondary)]' />
|
||||
{isRenaming ? (
|
||||
<Check className='size-[14px]' />
|
||||
) : (
|
||||
<Pencil className='size-[14px]' />
|
||||
)}
|
||||
</Button>
|
||||
) : (
|
||||
<div className='flex items-center justify-center'>
|
||||
<Lock className='size-[14px] text-[var(--text-secondary)]' />
|
||||
</div>
|
||||
)}
|
||||
</Tooltip.Trigger>
|
||||
<Tooltip.Content side='top'>
|
||||
<p>
|
||||
{isAncestorLocked
|
||||
? 'Ancestor container is locked'
|
||||
: userPermissions.canAdmin && currentBlock.locked
|
||||
? 'Unlock block'
|
||||
: 'Block is locked'}
|
||||
</p>
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Root>
|
||||
)}
|
||||
{/* Rename button */}
|
||||
{currentBlock && (
|
||||
<Tooltip.Root>
|
||||
<Tooltip.Trigger asChild>
|
||||
<Button
|
||||
variant='ghost'
|
||||
className='p-0'
|
||||
onClick={isRenaming ? handleSaveRename : handleStartRename}
|
||||
disabled={!canEditBlock}
|
||||
aria-label={isRenaming ? 'Save name' : 'Rename block'}
|
||||
>
|
||||
{isRenaming ? (
|
||||
<Check className='size-[14px]' />
|
||||
) : (
|
||||
<Pencil className='size-[14px]' />
|
||||
)}
|
||||
</Button>
|
||||
</Tooltip.Trigger>
|
||||
<Tooltip.Content side='top'>
|
||||
<p>{isRenaming ? 'Save name' : 'Rename block'}</p>
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Root>
|
||||
)}
|
||||
{/* Focus on block button */}
|
||||
{/* {currentBlock && (
|
||||
</Tooltip.Trigger>
|
||||
<Tooltip.Content side='top'>
|
||||
<p>{isRenaming ? 'Save name' : 'Rename block'}</p>
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Root>
|
||||
)}
|
||||
{/* Focus on block button */}
|
||||
{/* {currentBlock && (
|
||||
<Tooltip.Root>
|
||||
<Tooltip.Trigger asChild>
|
||||
<Button
|
||||
@@ -488,291 +490,291 @@ export function Editor() {
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Root>
|
||||
)} */}
|
||||
<Tooltip.Root>
|
||||
<Tooltip.Trigger asChild>
|
||||
<Button
|
||||
variant='ghost'
|
||||
className='p-0'
|
||||
onClick={handleOpenDocs}
|
||||
aria-label='Open documentation'
|
||||
>
|
||||
<BookOpen className='size-[14px]' />
|
||||
</Button>
|
||||
</Tooltip.Trigger>
|
||||
<Tooltip.Content side='top'>
|
||||
<p>Open docs</p>
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Root>
|
||||
<Tooltip.Root>
|
||||
<Tooltip.Trigger asChild>
|
||||
<Button
|
||||
variant='ghost'
|
||||
className='p-0'
|
||||
onClick={handleOpenDocs}
|
||||
aria-label='Open documentation'
|
||||
>
|
||||
<BookOpen className='size-[14px]' />
|
||||
</Button>
|
||||
</Tooltip.Trigger>
|
||||
<Tooltip.Content side='top'>
|
||||
<p>Open docs</p>
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Root>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{!currentBlockId || !currentBlock ? (
|
||||
<div className='flex flex-1 items-center justify-center text-[var(--text-placeholder)] text-small'>
|
||||
Select a block to edit
|
||||
</div>
|
||||
) : isSubflow ? (
|
||||
<SubflowEditor
|
||||
currentBlock={currentBlock}
|
||||
currentBlockId={currentBlockId}
|
||||
subBlocksRef={subBlocksRef}
|
||||
connectionsHeight={connectionsHeight}
|
||||
isResizing={isResizing}
|
||||
hasIncomingConnections={hasIncomingConnections}
|
||||
incomingConnections={incomingConnections}
|
||||
handleConnectionsResizeMouseDown={handleConnectionsResizeMouseDown}
|
||||
toggleConnectionsCollapsed={toggleConnectionsCollapsed}
|
||||
userCanEdit={canEditBlock}
|
||||
isConnectionsAtMinHeight={isConnectionsAtMinHeight}
|
||||
activeSearchTarget={activeSearchTargetForCurrentBlock}
|
||||
/>
|
||||
) : (
|
||||
<div className='flex flex-1 flex-col overflow-hidden pt-[0px]'>
|
||||
{/* Subblocks Section */}
|
||||
<div
|
||||
ref={subBlocksRef}
|
||||
className='subblocks-section flex flex-1 flex-col overflow-hidden'
|
||||
>
|
||||
<div className='flex-1 overflow-y-auto overflow-x-hidden px-2 pt-3 pb-2 [overflow-anchor:none]'>
|
||||
{/* Workflow Preview - only for workflow blocks with a selected child workflow */}
|
||||
{isWorkflowBlock && childWorkflowId && (
|
||||
<>
|
||||
<div className='subblock-content flex flex-col gap-[9.5px]'>
|
||||
<div className='pl-0.5 font-medium text-[var(--text-primary)] text-small leading-none'>
|
||||
Workflow Preview
|
||||
</div>
|
||||
<div className='relative h-[160px] overflow-hidden rounded-sm border border-[var(--border)]'>
|
||||
{isLoadingChildWorkflow ? (
|
||||
<div className='flex h-full items-center justify-center bg-[var(--surface-3)]'>
|
||||
<Loader className='size-5 text-[var(--text-tertiary)]' animate />
|
||||
</div>
|
||||
) : childWorkflowState ? (
|
||||
<>
|
||||
<div className='[&_*:active]:!cursor-grabbing [&_*]:!cursor-grab [&_.react-flow__handle]:!hidden h-full w-full'>
|
||||
<PreviewWorkflow
|
||||
workflowState={childWorkflowState}
|
||||
height={160}
|
||||
width='100%'
|
||||
isPannable={true}
|
||||
defaultZoom={0.6}
|
||||
fitPadding={0.15}
|
||||
cursorStyle='grab'
|
||||
lightweight
|
||||
/>
|
||||
</div>
|
||||
<Tooltip.Root>
|
||||
<Tooltip.Trigger asChild>
|
||||
<Button
|
||||
type='button'
|
||||
variant='ghost'
|
||||
onClick={handleOpenChildWorkflow}
|
||||
className='absolute right-[6px] bottom-1.5 z-10 size-[24px] cursor-pointer border border-[var(--border)] bg-[var(--surface-2)] p-0 hover-hover:bg-[var(--surface-4)]'
|
||||
>
|
||||
<ExternalLink className='size-[12px]' />
|
||||
</Button>
|
||||
</Tooltip.Trigger>
|
||||
<Tooltip.Content side='top'>Open workflow</Tooltip.Content>
|
||||
</Tooltip.Root>
|
||||
</>
|
||||
) : (
|
||||
<div className='flex h-full items-center justify-center bg-[var(--surface-3)]'>
|
||||
<span className='text-[var(--text-tertiary)] text-small'>
|
||||
Unable to load preview
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<FieldDivider subblockMarker />
|
||||
</>
|
||||
)}
|
||||
{subBlocks.length === 0 && !isWorkflowBlock ? (
|
||||
<div className='flex h-full items-center justify-center text-center text-[var(--text-placeholder)] text-small'>
|
||||
This block has no subblocks
|
||||
</div>
|
||||
) : (
|
||||
<div className='flex flex-col'>
|
||||
{regularSubBlocks.map((subBlock, index) => {
|
||||
const stableKey = getSubBlockStableKey(
|
||||
currentBlockId || '',
|
||||
subBlock,
|
||||
subBlockState
|
||||
)
|
||||
const canonicalId = canonicalIndex.canonicalIdBySubBlockId[subBlock.id]
|
||||
const canonicalGroup = canonicalId
|
||||
? canonicalIndex.groupsById[canonicalId]
|
||||
: undefined
|
||||
const isCanonicalSwap = isCanonicalPair(canonicalGroup)
|
||||
const canonicalMode =
|
||||
canonicalGroup && isCanonicalSwap
|
||||
? resolveCanonicalMode(
|
||||
canonicalGroup,
|
||||
blockSubBlockValues,
|
||||
canonicalModeOverrides
|
||||
)
|
||||
: undefined
|
||||
|
||||
const showDivider =
|
||||
index < regularSubBlocks.length - 1 ||
|
||||
(!hasAdvancedOnlyFields && index < subBlocks.length - 1)
|
||||
|
||||
return (
|
||||
<div key={stableKey} className='subblock-row'>
|
||||
<SubBlock
|
||||
blockId={currentBlockId}
|
||||
config={subBlock}
|
||||
isPreview={false}
|
||||
subBlockValues={subBlockState}
|
||||
disabled={!canEditBlock}
|
||||
allowExpandInPreview={false}
|
||||
isSearchHighlighted={
|
||||
activeSearchTarget?.blockId === currentBlockId &&
|
||||
(activeSearchTarget.subBlockId === subBlock.id ||
|
||||
activeSearchTarget.canonicalSubBlockId ===
|
||||
(subBlock.canonicalParamId ?? subBlock.id))
|
||||
}
|
||||
activeSearchTarget={activeSearchTargetForCurrentBlock}
|
||||
canonicalToggle={
|
||||
isCanonicalSwap && canonicalMode && canonicalId
|
||||
? {
|
||||
mode: canonicalMode,
|
||||
disabled: !canEditBlock,
|
||||
onToggle: () => {
|
||||
if (!currentBlockId) return
|
||||
const nextMode =
|
||||
canonicalMode === 'advanced' ? 'basic' : 'advanced'
|
||||
collaborativeSetBlockCanonicalMode(
|
||||
currentBlockId,
|
||||
canonicalId,
|
||||
nextMode
|
||||
)
|
||||
},
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
{showDivider && <FieldDivider subblockMarker />}
|
||||
{!currentBlockId || !currentBlock ? (
|
||||
<div className='flex flex-1 items-center justify-center text-[var(--text-placeholder)] text-small'>
|
||||
Select a block to edit
|
||||
</div>
|
||||
) : isSubflow ? (
|
||||
<SubflowEditor
|
||||
currentBlock={currentBlock}
|
||||
currentBlockId={currentBlockId}
|
||||
subBlocksRef={subBlocksRef}
|
||||
connectionsHeight={connectionsHeight}
|
||||
isResizing={isResizing}
|
||||
hasIncomingConnections={hasIncomingConnections}
|
||||
incomingConnections={incomingConnections}
|
||||
handleConnectionsResizeMouseDown={handleConnectionsResizeMouseDown}
|
||||
toggleConnectionsCollapsed={toggleConnectionsCollapsed}
|
||||
userCanEdit={canEditBlock}
|
||||
isConnectionsAtMinHeight={isConnectionsAtMinHeight}
|
||||
/>
|
||||
) : (
|
||||
<div className='flex flex-1 flex-col overflow-hidden pt-[0px]'>
|
||||
{/* Subblocks Section */}
|
||||
<div
|
||||
ref={subBlocksRef}
|
||||
className='subblocks-section flex flex-1 flex-col overflow-hidden'
|
||||
>
|
||||
<div className='flex-1 overflow-y-auto overflow-x-hidden px-2 pt-3 pb-2 [overflow-anchor:none]'>
|
||||
{/* Workflow Preview - only for workflow blocks with a selected child workflow */}
|
||||
{isWorkflowBlock && childWorkflowId && (
|
||||
<>
|
||||
<div className='subblock-content flex flex-col gap-[9.5px]'>
|
||||
<div className='pl-0.5 font-medium text-[var(--text-primary)] text-small leading-none'>
|
||||
Workflow Preview
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
|
||||
{hasAdvancedOnlyFields && canEditBlock && (
|
||||
<div className='flex items-center gap-2.5 px-0.5 pt-3.5 pb-3'>
|
||||
<div className='h-[1.25px] flex-1' style={DASHED_DIVIDER_STYLE} />
|
||||
<button
|
||||
type='button'
|
||||
onClick={handleToggleAdvancedMode}
|
||||
className='flex items-center gap-1.5 whitespace-nowrap font-medium text-[var(--text-secondary)] text-small hover-hover:text-[var(--text-primary)]'
|
||||
>
|
||||
{displayAdvancedOptions
|
||||
? 'Hide additional fields'
|
||||
: 'Show additional fields'}
|
||||
<ChevronDown
|
||||
className={`size-[14px] transition-transform duration-200 ${displayAdvancedOptions ? 'rotate-180' : ''}`}
|
||||
/>
|
||||
</button>
|
||||
<div className='h-[1.25px] flex-1' style={DASHED_DIVIDER_STYLE} />
|
||||
</div>
|
||||
)}
|
||||
{hasAdvancedOnlyFields && !canEditBlock && displayAdvancedOptions && (
|
||||
<div className='flex items-center gap-2.5 px-0.5 pt-3.5 pb-3'>
|
||||
<div className='h-[1.25px] flex-1' style={DASHED_DIVIDER_STYLE} />
|
||||
<span className='whitespace-nowrap font-medium text-[var(--text-secondary)] text-small'>
|
||||
Additional fields
|
||||
</span>
|
||||
<div className='h-[1.25px] flex-1' style={DASHED_DIVIDER_STYLE} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{advancedOnlySubBlocks.map((subBlock, index) => {
|
||||
const stableKey = getSubBlockStableKey(
|
||||
currentBlockId || '',
|
||||
subBlock,
|
||||
subBlockState
|
||||
)
|
||||
|
||||
return (
|
||||
<div key={stableKey} className='subblock-row'>
|
||||
<SubBlock
|
||||
blockId={currentBlockId}
|
||||
config={subBlock}
|
||||
isPreview={false}
|
||||
subBlockValues={subBlockState}
|
||||
disabled={!canEditBlock}
|
||||
allowExpandInPreview={false}
|
||||
isSearchHighlighted={
|
||||
activeSearchTarget?.blockId === currentBlockId &&
|
||||
(activeSearchTarget.subBlockId === subBlock.id ||
|
||||
activeSearchTarget.canonicalSubBlockId ===
|
||||
(subBlock.canonicalParamId ?? subBlock.id))
|
||||
}
|
||||
activeSearchTarget={activeSearchTargetForCurrentBlock}
|
||||
/>
|
||||
{index < advancedOnlySubBlocks.length - 1 && (
|
||||
<FieldDivider subblockMarker />
|
||||
<div className='relative h-[160px] overflow-hidden rounded-sm border border-[var(--border)]'>
|
||||
{isLoadingChildWorkflow ? (
|
||||
<div className='flex h-full items-center justify-center bg-[var(--surface-3)]'>
|
||||
<Loader className='size-5 text-[var(--text-tertiary)]' animate />
|
||||
</div>
|
||||
) : childWorkflowState ? (
|
||||
<>
|
||||
<div className='[&_*:active]:!cursor-grabbing [&_*]:!cursor-grab [&_.react-flow__handle]:!hidden h-full w-full'>
|
||||
<PreviewWorkflow
|
||||
workflowState={childWorkflowState}
|
||||
height={160}
|
||||
width='100%'
|
||||
isPannable={true}
|
||||
defaultZoom={0.6}
|
||||
fitPadding={0.15}
|
||||
cursorStyle='grab'
|
||||
lightweight
|
||||
/>
|
||||
</div>
|
||||
<Tooltip.Root>
|
||||
<Tooltip.Trigger asChild>
|
||||
<Button
|
||||
type='button'
|
||||
variant='ghost'
|
||||
onClick={handleOpenChildWorkflow}
|
||||
className='absolute right-[6px] bottom-1.5 z-10 size-[24px] cursor-pointer border border-[var(--border)] bg-[var(--surface-2)] p-0 hover-hover:bg-[var(--surface-4)]'
|
||||
>
|
||||
<ExternalLink className='size-[12px]' />
|
||||
</Button>
|
||||
</Tooltip.Trigger>
|
||||
<Tooltip.Content side='top'>Open workflow</Tooltip.Content>
|
||||
</Tooltip.Root>
|
||||
</>
|
||||
) : (
|
||||
<div className='flex h-full items-center justify-center bg-[var(--surface-3)]'>
|
||||
<span className='text-[var(--text-tertiary)] text-small'>
|
||||
Unable to load preview
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<FieldDivider subblockMarker />
|
||||
</>
|
||||
)}
|
||||
{subBlocks.length === 0 && !isWorkflowBlock ? (
|
||||
<div className='flex h-full items-center justify-center text-center text-[var(--text-placeholder)] text-small'>
|
||||
This block has no subblocks
|
||||
</div>
|
||||
) : (
|
||||
<div className='flex flex-col'>
|
||||
{regularSubBlocks.map((subBlock, index) => {
|
||||
const stableKey = getSubBlockStableKey(
|
||||
currentBlockId || '',
|
||||
subBlock,
|
||||
subBlockState
|
||||
)
|
||||
const canonicalId = canonicalIndex.canonicalIdBySubBlockId[subBlock.id]
|
||||
const canonicalGroup = canonicalId
|
||||
? canonicalIndex.groupsById[canonicalId]
|
||||
: undefined
|
||||
const isCanonicalSwap = isCanonicalPair(canonicalGroup)
|
||||
const canonicalMode =
|
||||
canonicalGroup && isCanonicalSwap
|
||||
? resolveCanonicalMode(
|
||||
canonicalGroup,
|
||||
blockSubBlockValues,
|
||||
canonicalModeOverrides
|
||||
)
|
||||
: undefined
|
||||
|
||||
{/* Connections Section - Only show when there are connections */}
|
||||
{hasIncomingConnections && (
|
||||
<div
|
||||
className={
|
||||
'connections-section flex flex-shrink-0 flex-col overflow-hidden border-[var(--border)] border-t' +
|
||||
(!isResizing ? ' transition-[height] duration-100 ease-out' : '')
|
||||
}
|
||||
style={{ height: `${connectionsHeight}px` }}
|
||||
>
|
||||
{/* Resize Handle */}
|
||||
<div className='relative'>
|
||||
<div
|
||||
role='separator'
|
||||
aria-orientation='horizontal'
|
||||
className='absolute top-[-4px] right-0 left-0 z-30 h-[8px] cursor-ns-resize'
|
||||
onMouseDown={handleConnectionsResizeMouseDown}
|
||||
/>
|
||||
const showDivider =
|
||||
index < regularSubBlocks.length - 1 ||
|
||||
(!hasAdvancedOnlyFields && index < subBlocks.length - 1)
|
||||
|
||||
return (
|
||||
<div key={stableKey} className='subblock-row'>
|
||||
<SubBlock
|
||||
blockId={currentBlockId}
|
||||
config={subBlock}
|
||||
isPreview={false}
|
||||
subBlockValues={subBlockState}
|
||||
disabled={!canEditBlock}
|
||||
allowExpandInPreview={false}
|
||||
isSearchHighlighted={
|
||||
activeSearchTarget?.blockId === currentBlockId &&
|
||||
(activeSearchTarget.subBlockId === subBlock.id ||
|
||||
activeSearchTarget.canonicalSubBlockId ===
|
||||
(subBlock.canonicalParamId ?? subBlock.id))
|
||||
}
|
||||
canonicalToggle={
|
||||
isCanonicalSwap && canonicalMode && canonicalId
|
||||
? {
|
||||
mode: canonicalMode,
|
||||
disabled: !canEditBlock,
|
||||
onToggle: () => {
|
||||
if (!currentBlockId) return
|
||||
const nextMode =
|
||||
canonicalMode === 'advanced' ? 'basic' : 'advanced'
|
||||
collaborativeSetBlockCanonicalMode(
|
||||
currentBlockId,
|
||||
canonicalId,
|
||||
nextMode
|
||||
)
|
||||
},
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
{showDivider && <FieldDivider subblockMarker />}
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
|
||||
{hasAdvancedOnlyFields && canEditBlock && (
|
||||
<div className='flex items-center gap-2.5 px-0.5 pt-3.5 pb-3'>
|
||||
<div className='h-[1.25px] flex-1' style={DASHED_DIVIDER_STYLE} />
|
||||
<button
|
||||
type='button'
|
||||
onClick={handleToggleAdvancedMode}
|
||||
className='flex items-center gap-1.5 whitespace-nowrap font-medium text-[var(--text-secondary)] text-small hover-hover:text-[var(--text-primary)]'
|
||||
>
|
||||
{displayAdvancedOptions
|
||||
? 'Hide additional fields'
|
||||
: 'Show additional fields'}
|
||||
<ChevronDown
|
||||
className={`size-[14px] transition-transform duration-200 ${displayAdvancedOptions ? 'rotate-180' : ''}`}
|
||||
/>
|
||||
</button>
|
||||
<div className='h-[1.25px] flex-1' style={DASHED_DIVIDER_STYLE} />
|
||||
</div>
|
||||
)}
|
||||
{hasAdvancedOnlyFields && !canEditBlock && displayAdvancedOptions && (
|
||||
<div className='flex items-center gap-2.5 px-0.5 pt-3.5 pb-3'>
|
||||
<div className='h-[1.25px] flex-1' style={DASHED_DIVIDER_STYLE} />
|
||||
<span className='whitespace-nowrap font-medium text-[var(--text-secondary)] text-small'>
|
||||
Additional fields
|
||||
</span>
|
||||
<div className='h-[1.25px] flex-1' style={DASHED_DIVIDER_STYLE} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{advancedOnlySubBlocks.map((subBlock, index) => {
|
||||
const stableKey = getSubBlockStableKey(
|
||||
currentBlockId || '',
|
||||
subBlock,
|
||||
subBlockState
|
||||
)
|
||||
|
||||
return (
|
||||
<div key={stableKey} className='subblock-row'>
|
||||
<SubBlock
|
||||
blockId={currentBlockId}
|
||||
config={subBlock}
|
||||
isPreview={false}
|
||||
subBlockValues={subBlockState}
|
||||
disabled={!canEditBlock}
|
||||
allowExpandInPreview={false}
|
||||
isSearchHighlighted={
|
||||
activeSearchTarget?.blockId === currentBlockId &&
|
||||
(activeSearchTarget.subBlockId === subBlock.id ||
|
||||
activeSearchTarget.canonicalSubBlockId ===
|
||||
(subBlock.canonicalParamId ?? subBlock.id))
|
||||
}
|
||||
/>
|
||||
{index < advancedOnlySubBlocks.length - 1 && (
|
||||
<FieldDivider subblockMarker />
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Connections Header with Chevron */}
|
||||
{/* Connections Section - Only show when there are connections */}
|
||||
{hasIncomingConnections && (
|
||||
<div
|
||||
className='flex flex-shrink-0 cursor-pointer items-center gap-2 px-2.5 pt-[5px] pb-[5px]'
|
||||
onClick={toggleConnectionsCollapsed}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter' || e.key === ' ') {
|
||||
e.preventDefault()
|
||||
toggleConnectionsCollapsed()
|
||||
}
|
||||
}}
|
||||
role='button'
|
||||
tabIndex={0}
|
||||
aria-label={
|
||||
isConnectionsAtMinHeight ? 'Expand connections' : 'Collapse connections'
|
||||
className={
|
||||
'connections-section flex flex-shrink-0 flex-col overflow-hidden border-[var(--border)] border-t' +
|
||||
(!isResizing ? ' transition-[height] duration-100 ease-out' : '')
|
||||
}
|
||||
style={{ height: `${connectionsHeight}px` }}
|
||||
>
|
||||
<ChevronUp
|
||||
className={
|
||||
'size-[14px] transition-transform' +
|
||||
(!isConnectionsAtMinHeight ? ' rotate-180' : '')
|
||||
}
|
||||
/>
|
||||
<div className='font-medium text-[var(--text-primary)] text-small'>Connections</div>
|
||||
</div>
|
||||
{/* Resize Handle */}
|
||||
<div className='relative'>
|
||||
<div
|
||||
role='separator'
|
||||
aria-orientation='horizontal'
|
||||
className='absolute top-[-4px] right-0 left-0 z-30 h-[8px] cursor-ns-resize'
|
||||
onMouseDown={handleConnectionsResizeMouseDown}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Connections Content - Always visible */}
|
||||
<div className='flex-1 overflow-y-auto overflow-x-hidden px-1.5 pb-2'>
|
||||
<ConnectionBlocks
|
||||
connections={incomingConnections}
|
||||
currentBlockId={currentBlock.id}
|
||||
/>
|
||||
{/* Connections Header with Chevron */}
|
||||
<div
|
||||
className='flex flex-shrink-0 cursor-pointer items-center gap-2 px-2.5 pt-[5px] pb-[5px]'
|
||||
onClick={toggleConnectionsCollapsed}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter' || e.key === ' ') {
|
||||
e.preventDefault()
|
||||
toggleConnectionsCollapsed()
|
||||
}
|
||||
}}
|
||||
role='button'
|
||||
tabIndex={0}
|
||||
aria-label={
|
||||
isConnectionsAtMinHeight ? 'Expand connections' : 'Collapse connections'
|
||||
}
|
||||
>
|
||||
<ChevronUp
|
||||
className={
|
||||
'size-[14px] transition-transform' +
|
||||
(!isConnectionsAtMinHeight ? ' rotate-180' : '')
|
||||
}
|
||||
/>
|
||||
<div className='font-medium text-[var(--text-primary)] text-small'>
|
||||
Connections
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Connections Content - Always visible */}
|
||||
<div className='flex-1 overflow-y-auto overflow-x-hidden px-1.5 pb-2'>
|
||||
<ConnectionBlocks
|
||||
connections={incomingConnections}
|
||||
currentBlockId={currentBlock.id}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</ActiveSearchTargetProvider>
|
||||
)
|
||||
}
|
||||
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
'use client'
|
||||
|
||||
import type React from 'react'
|
||||
import { createContext, useContext } from 'react'
|
||||
import type { ActiveSearchTarget } from '@/stores/panel/editor/store'
|
||||
|
||||
const ActiveSearchTargetContext = createContext<ActiveSearchTarget | null>(null)
|
||||
|
||||
interface ActiveSearchTargetProviderProps {
|
||||
value: ActiveSearchTarget | null
|
||||
children: React.ReactNode
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides the active workflow-search target to the panel editor sub-block tree.
|
||||
*
|
||||
* @remarks
|
||||
* The editor provides the target scoped to the currently edited block. Components
|
||||
* that project sub-block values into synthetic sub-blocks (e.g. tool-input params)
|
||||
* re-provide a transformed target so nested inputs receive the rewritten
|
||||
* `subBlockId`/`valuePath`. Outside any provider (e.g. preview), consumers see
|
||||
* `null`, which disables search highlighting.
|
||||
*/
|
||||
export function ActiveSearchTargetProvider({ value, children }: ActiveSearchTargetProviderProps) {
|
||||
return (
|
||||
<ActiveSearchTargetContext.Provider value={value}>
|
||||
{children}
|
||||
</ActiveSearchTargetContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the active workflow-search target for the nearest editor scope, or
|
||||
* `null` when no search target applies (no provider, or target outside scope).
|
||||
*/
|
||||
export function useActiveSearchTarget(): ActiveSearchTarget | null {
|
||||
return useContext(ActiveSearchTargetContext)
|
||||
}
|
||||
+9
-24
@@ -18,7 +18,7 @@ import {
|
||||
useFolderExpand,
|
||||
useItemDrag,
|
||||
useItemRename,
|
||||
useSidebarDragContext,
|
||||
useSidebarListContext,
|
||||
} from '@/app/workspace/[workspaceId]/w/components/sidebar/hooks'
|
||||
import { SIDEBAR_SCROLL_EVENT } from '@/app/workspace/[workspaceId]/w/components/sidebar/sidebar'
|
||||
import {
|
||||
@@ -51,25 +51,11 @@ const logger = createLogger('FolderItem')
|
||||
|
||||
interface FolderItemProps {
|
||||
folder: FolderTreeNode
|
||||
dragDisabled?: boolean
|
||||
hoverHandlers?: {
|
||||
onDragEnter?: (e: React.DragEvent<HTMLElement>) => void
|
||||
onDragLeave?: (e: React.DragEvent<HTMLElement>) => void
|
||||
}
|
||||
onFolderClick?: (folderId: string, shiftKey: boolean, metaKey: boolean) => void
|
||||
onDragStart?: () => void
|
||||
onDragEnd?: () => void
|
||||
}
|
||||
|
||||
export function FolderItem({
|
||||
folder,
|
||||
dragDisabled = false,
|
||||
hoverHandlers,
|
||||
onFolderClick,
|
||||
onDragStart: onDragStartProp,
|
||||
onDragEnd: onDragEndProp,
|
||||
}: FolderItemProps) {
|
||||
const { isAnyDragActive } = useSidebarDragContext()
|
||||
export function FolderItem({ folder }: FolderItemProps) {
|
||||
const { isAnyDragActive, dragDisabled, onFolderClick, onItemDragStart, onItemDragEnd } =
|
||||
useSidebarListContext()
|
||||
const params = useParams()
|
||||
const router = useRouter()
|
||||
const workspaceId = params.workspaceId as string
|
||||
@@ -234,9 +220,9 @@ export function FolderItem({
|
||||
e.dataTransfer.setDragImage(ghost, ghost.offsetWidth / 2, ghost.offsetHeight / 2)
|
||||
dragGhostRef.current = ghost
|
||||
|
||||
onDragStartProp?.()
|
||||
onItemDragStart(folder.parentId)
|
||||
},
|
||||
[folder.id, folder.name, workspaceId, onDragStartProp]
|
||||
[folder.id, folder.name, folder.parentId, workspaceId, onItemDragStart]
|
||||
)
|
||||
|
||||
const {
|
||||
@@ -254,8 +240,8 @@ export function FolderItem({
|
||||
dragGhostRef.current = null
|
||||
}
|
||||
handleDragEndBase()
|
||||
onDragEndProp?.()
|
||||
}, [handleDragEndBase, onDragEndProp])
|
||||
onItemDragEnd()
|
||||
}, [handleDragEndBase, onItemDragEnd])
|
||||
|
||||
const {
|
||||
isOpen: isContextMenuOpen,
|
||||
@@ -362,7 +348,7 @@ export function FolderItem({
|
||||
|
||||
const isModifierClick = e.shiftKey || e.metaKey || e.ctrlKey
|
||||
|
||||
if (isModifierClick && onFolderClick) {
|
||||
if (isModifierClick) {
|
||||
e.preventDefault()
|
||||
onFolderClick(folder.id, e.shiftKey, e.metaKey || e.ctrlKey)
|
||||
return
|
||||
@@ -496,7 +482,6 @@ export function FolderItem({
|
||||
draggable={!isEditing && !dragDisabled && !effectiveLocked}
|
||||
onDragStart={handleDragStart}
|
||||
onDragEnd={handleDragEnd}
|
||||
{...hoverHandlers}
|
||||
>
|
||||
<ChevronRight
|
||||
className={clsx(
|
||||
|
||||
+10
-19
@@ -17,7 +17,7 @@ import {
|
||||
useContextMenu,
|
||||
useItemDrag,
|
||||
useItemRename,
|
||||
useSidebarDragContext,
|
||||
useSidebarListContext,
|
||||
} from '@/app/workspace/[workspaceId]/w/components/sidebar/hooks'
|
||||
import {
|
||||
buildDragResources,
|
||||
@@ -46,28 +46,19 @@ import type { WorkflowMetadata } from '@/stores/workflows/registry/types'
|
||||
interface WorkflowItemProps {
|
||||
workflow: WorkflowMetadata
|
||||
active: boolean
|
||||
dragDisabled?: boolean
|
||||
onWorkflowClick: (workflowId: string, shiftKey: boolean) => void
|
||||
onDragStart?: () => void
|
||||
onDragEnd?: () => void
|
||||
}
|
||||
|
||||
/**
|
||||
* WorkflowItem component displaying a single workflow with drag and selection support.
|
||||
* Uses the item drag hook for unified drag behavior.
|
||||
* Selection and drag callbacks come from the sidebar list context; uses the item drag
|
||||
* hook for unified drag behavior.
|
||||
*
|
||||
* @param props - Component props
|
||||
* @returns Workflow item with drag and selection support
|
||||
*/
|
||||
export function WorkflowItem({
|
||||
workflow,
|
||||
active,
|
||||
dragDisabled = false,
|
||||
onWorkflowClick,
|
||||
onDragStart: onDragStartProp,
|
||||
onDragEnd: onDragEndProp,
|
||||
}: WorkflowItemProps) {
|
||||
const { isAnyDragActive } = useSidebarDragContext()
|
||||
export function WorkflowItem({ workflow, active }: WorkflowItemProps) {
|
||||
const { isAnyDragActive, dragDisabled, onWorkflowClick, onItemDragStart, onItemDragEnd } =
|
||||
useSidebarListContext()
|
||||
const params = useParams()
|
||||
const workspaceId = params.workspaceId as string
|
||||
const selectedWorkflows = useFolderStore((state) => state.selectedWorkflows)
|
||||
@@ -345,9 +336,9 @@ export function WorkflowItem({
|
||||
e.dataTransfer.setDragImage(ghost, ghost.offsetWidth / 2, ghost.offsetHeight / 2)
|
||||
dragGhostRef.current = ghost
|
||||
|
||||
onDragStartProp?.()
|
||||
onItemDragStart(workflow.folderId || null)
|
||||
},
|
||||
[workflow.id, workflow.name, workspaceId, onDragStartProp]
|
||||
[workflow.id, workflow.name, workflow.folderId, workspaceId, onItemDragStart]
|
||||
)
|
||||
|
||||
const {
|
||||
@@ -365,8 +356,8 @@ export function WorkflowItem({
|
||||
dragGhostRef.current = null
|
||||
}
|
||||
handleDragEndBase()
|
||||
onDragEndProp?.()
|
||||
}, [handleDragEndBase, onDragEndProp])
|
||||
onItemDragEnd()
|
||||
}, [handleDragEndBase, onItemDragEnd])
|
||||
|
||||
const handleDoubleClick = useCallback(
|
||||
(e: React.MouseEvent) => {
|
||||
|
||||
+16
-47
@@ -8,11 +8,11 @@ import { EmptyAreaContextMenu } from '@/app/workspace/[workspaceId]/w/components
|
||||
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'
|
||||
import {
|
||||
SidebarDragContext,
|
||||
SidebarListContext,
|
||||
useContextMenu,
|
||||
useDragDrop,
|
||||
useFolderSelection,
|
||||
useSidebarDragContextValue,
|
||||
useSidebarListContextValue,
|
||||
useWorkflowSelection,
|
||||
} from '@/app/workspace/[workspaceId]/w/components/sidebar/hooks'
|
||||
import {
|
||||
@@ -34,8 +34,6 @@ interface WorkflowListProps {
|
||||
regularWorkflows: WorkflowMetadata[]
|
||||
isLoading?: boolean
|
||||
canReorder?: boolean
|
||||
handleFileChange: (event: React.ChangeEvent<HTMLInputElement>) => void
|
||||
fileInputRef: React.RefObject<HTMLInputElement | null>
|
||||
scrollContainerRef: React.RefObject<HTMLDivElement | null>
|
||||
onCreateWorkflow?: () => void
|
||||
onCreateFolder?: () => void
|
||||
@@ -71,8 +69,6 @@ export const WorkflowList = memo(function WorkflowList({
|
||||
regularWorkflows,
|
||||
isLoading = false,
|
||||
canReorder = true,
|
||||
handleFileChange,
|
||||
fileInputRef,
|
||||
scrollContainerRef,
|
||||
onCreateWorkflow,
|
||||
onCreateFolder,
|
||||
@@ -110,8 +106,6 @@ export const WorkflowList = memo(function WorkflowList({
|
||||
handleDragEnd,
|
||||
} = useDragDrop({ disabled: !canReorder })
|
||||
|
||||
const dragContextValue = useSidebarDragContextValue(isDragging)
|
||||
|
||||
useEffect(() => {
|
||||
if (scrollContainerRef.current) {
|
||||
setScrollContainer(scrollContainerRef.current)
|
||||
@@ -347,6 +341,15 @@ export const WorkflowList = memo(function WorkflowList({
|
||||
folderDescendantIds,
|
||||
})
|
||||
|
||||
const listContextValue = useSidebarListContextValue({
|
||||
isAnyDragActive: isDragging,
|
||||
dragDisabled,
|
||||
onWorkflowClick: handleWorkflowClick,
|
||||
onFolderClick: handleFolderClick,
|
||||
onItemDragStart: handleDragStart,
|
||||
onItemDragEnd: handleDragEnd,
|
||||
})
|
||||
|
||||
const isWorkflowActive = useCallback((wfId: string) => wfId === workflowId, [workflowId])
|
||||
|
||||
useEffect(() => {
|
||||
@@ -377,28 +380,13 @@ export const WorkflowList = memo(function WorkflowList({
|
||||
style={{ paddingLeft: `${level * TREE_SPACING.INDENT_PER_LEVEL}px` }}
|
||||
{...createWorkflowDragHandlers(workflow.id, folderId)}
|
||||
>
|
||||
<WorkflowItem
|
||||
workflow={workflow}
|
||||
active={isWorkflowActive(workflow.id)}
|
||||
dragDisabled={dragDisabled}
|
||||
onWorkflowClick={handleWorkflowClick}
|
||||
onDragStart={() => handleDragStart(folderId)}
|
||||
onDragEnd={handleDragEnd}
|
||||
/>
|
||||
<WorkflowItem workflow={workflow} active={isWorkflowActive(workflow.id)} />
|
||||
</div>
|
||||
<DropIndicatorLine show={showAfter} level={level} position='after' />
|
||||
</div>
|
||||
)
|
||||
},
|
||||
[
|
||||
dropIndicator,
|
||||
isWorkflowActive,
|
||||
dragDisabled,
|
||||
createWorkflowDragHandlers,
|
||||
handleWorkflowClick,
|
||||
handleDragStart,
|
||||
handleDragEnd,
|
||||
]
|
||||
[dropIndicator, isWorkflowActive, createWorkflowDragHandlers]
|
||||
)
|
||||
|
||||
const renderFolderSection = useCallback(
|
||||
@@ -457,13 +445,7 @@ export const WorkflowList = memo(function WorkflowList({
|
||||
style={{ paddingLeft: `${level * TREE_SPACING.INDENT_PER_LEVEL}px` }}
|
||||
{...createFolderDragHandlers(folder.id, parentFolderId)}
|
||||
>
|
||||
<FolderItem
|
||||
folder={folder}
|
||||
dragDisabled={dragDisabled}
|
||||
onFolderClick={handleFolderClick}
|
||||
onDragStart={() => handleDragStart(parentFolderId)}
|
||||
onDragEnd={handleDragEnd}
|
||||
/>
|
||||
<FolderItem folder={folder} />
|
||||
</div>
|
||||
<DropIndicatorLine show={showAfter} level={level} position='after' />
|
||||
|
||||
@@ -493,13 +475,9 @@ export const WorkflowList = memo(function WorkflowList({
|
||||
expandedFolders,
|
||||
dropIndicator,
|
||||
isDragging,
|
||||
dragDisabled,
|
||||
createFolderDragHandlers,
|
||||
createEmptyFolderDropZone,
|
||||
createFolderContentDropZone,
|
||||
handleDragStart,
|
||||
handleDragEnd,
|
||||
handleFolderClick,
|
||||
renderWorkflowItem,
|
||||
]
|
||||
)
|
||||
@@ -565,7 +543,7 @@ export const WorkflowList = memo(function WorkflowList({
|
||||
)
|
||||
|
||||
return (
|
||||
<SidebarDragContext.Provider value={dragContextValue}>
|
||||
<SidebarListContext.Provider value={listContextValue}>
|
||||
<div
|
||||
role='tree'
|
||||
aria-label='Workflows'
|
||||
@@ -612,15 +590,6 @@ export const WorkflowList = memo(function WorkflowList({
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<input
|
||||
ref={fileInputRef}
|
||||
type='file'
|
||||
accept='.json,.zip'
|
||||
multiple
|
||||
style={{ display: 'none' }}
|
||||
onChange={handleFileChange}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{onCreateWorkflow && onCreateFolder && (
|
||||
@@ -635,6 +604,6 @@ export const WorkflowList = memo(function WorkflowList({
|
||||
disableCreateFolder={disableCreate}
|
||||
/>
|
||||
)}
|
||||
</SidebarDragContext.Provider>
|
||||
</SidebarListContext.Provider>
|
||||
)
|
||||
})
|
||||
|
||||
@@ -10,10 +10,10 @@ export { useHoverMenu } from './use-hover-menu'
|
||||
export { useItemDrag } from './use-item-drag'
|
||||
export { useItemRename } from './use-item-rename'
|
||||
export {
|
||||
SidebarDragContext,
|
||||
useSidebarDragContext,
|
||||
useSidebarDragContextValue,
|
||||
} from './use-sidebar-drag-context'
|
||||
SidebarListContext,
|
||||
useSidebarListContext,
|
||||
useSidebarListContextValue,
|
||||
} from './use-sidebar-list-context'
|
||||
export { useSidebarResize } from './use-sidebar-resize'
|
||||
export { useWorkflowOperations } from './use-workflow-operations'
|
||||
export { useWorkflowSelection } from './use-workflow-selection'
|
||||
|
||||
@@ -31,6 +31,17 @@ type SiblingItem = {
|
||||
createdAt: Date
|
||||
}
|
||||
|
||||
/** Stable no-op drop-zone handlers returned when drag-and-drop is disabled. */
|
||||
const NOOP_DRAG_HANDLERS = {
|
||||
onDragOver: (e: React.DragEvent<HTMLElement>) => e.preventDefault(),
|
||||
onDrop: (e: React.DragEvent<HTMLElement>) => e.preventDefault(),
|
||||
onDragLeave: () => {},
|
||||
}
|
||||
|
||||
const createNoopDragHandlers = () => NOOP_DRAG_HANDLERS
|
||||
|
||||
const noop = () => {}
|
||||
|
||||
/** Root folder vs root workflow scope: API/cache may use null or undefined for "no parent". */
|
||||
function isSameFolderScope(
|
||||
parentOrFolderId: string | null | undefined,
|
||||
@@ -650,26 +661,20 @@ export function useDragDrop(options: UseDragDropOptions = {}) {
|
||||
scrollContainerRef.current = element
|
||||
}, [])
|
||||
|
||||
const noopDragHandlers = {
|
||||
onDragOver: (e: React.DragEvent<HTMLElement>) => e.preventDefault(),
|
||||
onDrop: (e: React.DragEvent<HTMLElement>) => e.preventDefault(),
|
||||
onDragLeave: () => {},
|
||||
}
|
||||
|
||||
if (disabled) {
|
||||
return {
|
||||
dropIndicator: null,
|
||||
isDragging: false,
|
||||
disabled: true,
|
||||
setScrollContainer,
|
||||
createWorkflowDragHandlers: () => noopDragHandlers,
|
||||
createFolderDragHandlers: () => noopDragHandlers,
|
||||
createEmptyFolderDropZone: () => noopDragHandlers,
|
||||
createFolderContentDropZone: () => noopDragHandlers,
|
||||
createRootDropZone: () => noopDragHandlers,
|
||||
createEdgeDropZone: () => noopDragHandlers,
|
||||
handleDragStart: () => {},
|
||||
handleDragEnd: () => {},
|
||||
createWorkflowDragHandlers: createNoopDragHandlers,
|
||||
createFolderDragHandlers: createNoopDragHandlers,
|
||||
createEmptyFolderDropZone: createNoopDragHandlers,
|
||||
createFolderContentDropZone: createNoopDragHandlers,
|
||||
createRootDropZone: createNoopDragHandlers,
|
||||
createEdgeDropZone: createNoopDragHandlers,
|
||||
handleDragStart: noop,
|
||||
handleDragEnd: noop,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
-36
@@ -1,36 +0,0 @@
|
||||
'use client'
|
||||
|
||||
import { createContext, useContext, useMemo } from 'react'
|
||||
|
||||
interface SidebarDragContextValue {
|
||||
/** Whether any drag operation is currently in progress */
|
||||
isAnyDragActive: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* Context for sharing drag state across sidebar components.
|
||||
* Eliminates prop drilling of isAnyDragActive through component tree.
|
||||
*/
|
||||
export const SidebarDragContext = createContext<SidebarDragContextValue>({
|
||||
isAnyDragActive: false,
|
||||
})
|
||||
|
||||
/**
|
||||
* Hook to access the sidebar drag state.
|
||||
* Use this in WorkflowItem, FolderItem, etc. to check if any drag is in progress.
|
||||
*
|
||||
* @returns The current drag state
|
||||
*/
|
||||
export function useSidebarDragContext(): SidebarDragContextValue {
|
||||
return useContext(SidebarDragContext)
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook to create the sidebar drag context value.
|
||||
*
|
||||
* @param isDragging - Whether a drag is currently in progress
|
||||
* @returns Context value to provide to SidebarDragContext.Provider
|
||||
*/
|
||||
export function useSidebarDragContextValue(isDragging: boolean): SidebarDragContextValue {
|
||||
return useMemo(() => ({ isAnyDragActive: isDragging }), [isDragging])
|
||||
}
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
'use client'
|
||||
|
||||
import { createContext, useContext, useMemo } from 'react'
|
||||
|
||||
interface SidebarListContextValue {
|
||||
/** Whether any drag operation is currently in progress */
|
||||
isAnyDragActive: boolean
|
||||
/** Whether item dragging is disabled (e.g. viewer permissions) */
|
||||
dragDisabled: boolean
|
||||
/** Selects a workflow on click (single or shift-range selection) */
|
||||
onWorkflowClick: (workflowId: string, shiftKey: boolean) => void
|
||||
/** Selects a folder on modifier-click (shift-range or cmd/ctrl-toggle selection) */
|
||||
onFolderClick: (folderId: string, shiftKey: boolean, metaKey: boolean) => void
|
||||
/** Notifies the list that an item drag started from the given parent folder */
|
||||
onItemDragStart: (parentFolderId: string | null) => void
|
||||
/** Notifies the list that an item drag ended */
|
||||
onItemDragEnd: () => void
|
||||
}
|
||||
|
||||
const noop = () => {}
|
||||
|
||||
/**
|
||||
* Context for sharing list-item interaction handlers and drag state across
|
||||
* sidebar workflow-list components. Eliminates prop drilling of selection
|
||||
* and drag callbacks into WorkflowItem/FolderItem.
|
||||
*/
|
||||
export const SidebarListContext = createContext<SidebarListContextValue>({
|
||||
isAnyDragActive: false,
|
||||
dragDisabled: false,
|
||||
onWorkflowClick: noop,
|
||||
onFolderClick: noop,
|
||||
onItemDragStart: noop,
|
||||
onItemDragEnd: noop,
|
||||
})
|
||||
|
||||
/**
|
||||
* Hook to access the sidebar list context.
|
||||
* Use this in WorkflowItem, FolderItem, etc. for selection/drag callbacks and drag state.
|
||||
*
|
||||
* @returns The current sidebar list context value
|
||||
*/
|
||||
export function useSidebarListContext(): SidebarListContextValue {
|
||||
return useContext(SidebarListContext)
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook to create a memoized sidebar list context value.
|
||||
*
|
||||
* @param value - The handlers and drag state to expose to list items
|
||||
* @returns Memoized context value to provide to SidebarListContext.Provider
|
||||
*/
|
||||
export function useSidebarListContextValue(
|
||||
value: SidebarListContextValue
|
||||
): SidebarListContextValue {
|
||||
const {
|
||||
isAnyDragActive,
|
||||
dragDisabled,
|
||||
onWorkflowClick,
|
||||
onFolderClick,
|
||||
onItemDragStart,
|
||||
onItemDragEnd,
|
||||
} = value
|
||||
|
||||
return useMemo(
|
||||
() => ({
|
||||
isAnyDragActive,
|
||||
dragDisabled,
|
||||
onWorkflowClick,
|
||||
onFolderClick,
|
||||
onItemDragStart,
|
||||
onItemDragEnd,
|
||||
}),
|
||||
[isAnyDragActive, dragDisabled, onWorkflowClick, onFolderClick, onItemDragStart, onItemDragEnd]
|
||||
)
|
||||
}
|
||||
@@ -1230,6 +1230,14 @@ export const Sidebar = memo(function Sidebar() {
|
||||
className='hidden'
|
||||
onChange={handleLogoFileChange}
|
||||
/>
|
||||
<input
|
||||
ref={fileInputRef}
|
||||
type='file'
|
||||
accept='.json,.zip'
|
||||
multiple
|
||||
className='hidden'
|
||||
onChange={handleImportFileChange}
|
||||
/>
|
||||
<div className='relative h-full'>
|
||||
<aside
|
||||
className='sidebar-container relative h-full overflow-hidden bg-[var(--surface-1)]'
|
||||
@@ -1594,8 +1602,6 @@ export const Sidebar = memo(function Sidebar() {
|
||||
regularWorkflows={regularWorkflows}
|
||||
isLoading={isLoading}
|
||||
canReorder={canEdit}
|
||||
handleFileChange={handleImportFileChange}
|
||||
fileInputRef={fileInputRef}
|
||||
scrollContainerRef={scrollContainerRef}
|
||||
onCreateWorkflow={handleCreateWorkflow}
|
||||
onCreateFolder={handleCreateFolder}
|
||||
|
||||
Reference in New Issue
Block a user