mirror of
https://github.com/simstudioai/sim.git
synced 2026-04-06 03:00:16 -04:00
Feat/add mothership manual workflow runs (#3520)
* Add run and open workflow buttons in workflow preview * Send log request message after manual workflow run * Make edges in embedded workflow non-editable * Change chat to pass in log as additional context * Revert "Change chat to pass in log as additional context" This reverts commite957dffb2f. * Revert "Send log request message after manual workflow run" This reverts commit0fb92751f0. * Move run and workflow icons to tab bar * Simplify boolean condition --------- Co-authored-by: Theodore Li <theo@sim.ai>
This commit is contained in:
@@ -1,2 +1,2 @@
|
||||
export { ResourceContent } from './resource-content'
|
||||
export { EmbeddedWorkflowActions, ResourceContent } from './resource-content'
|
||||
export { ResourceTabs } from './resource-tabs'
|
||||
|
||||
@@ -1 +1 @@
|
||||
export { ResourceContent } from './resource-content'
|
||||
export { EmbeddedWorkflowActions, ResourceContent } from './resource-content'
|
||||
|
||||
@@ -1,14 +1,23 @@
|
||||
'use client'
|
||||
|
||||
import { lazy, Suspense, useMemo } from 'react'
|
||||
import { Skeleton } from '@/components/emcn'
|
||||
import { lazy, Suspense, useCallback, useEffect, useMemo } from 'react'
|
||||
import { Square } from 'lucide-react'
|
||||
import { useRouter } from 'next/navigation'
|
||||
import { Button, PlayOutline, Skeleton, Tooltip } from '@/components/emcn'
|
||||
import { WorkflowIcon } from '@/components/icons'
|
||||
import {
|
||||
FileViewer,
|
||||
type PreviewMode,
|
||||
} from '@/app/workspace/[workspaceId]/files/components/file-viewer'
|
||||
import { useWorkspacePermissionsContext } from '@/app/workspace/[workspaceId]/providers/workspace-permissions-provider'
|
||||
import type { MothershipResource } from '@/app/workspace/[workspaceId]/home/types'
|
||||
import { useWorkflowExecution } from '@/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-workflow-execution'
|
||||
import { useUsageLimits } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/hooks'
|
||||
import { Table } from '@/app/workspace/[workspaceId]/tables/[tableId]/components'
|
||||
import { useWorkspaceFiles } from '@/hooks/queries/workspace-files'
|
||||
import { useSettingsNavigation } from '@/hooks/use-settings-navigation'
|
||||
import { useCurrentWorkflowExecution } from '@/stores/execution'
|
||||
import { useWorkflowRegistry } from '@/stores/workflows/registry/store'
|
||||
|
||||
const Workflow = lazy(() => import('@/app/workspace/[workspaceId]/w/[workflowId]/workflow'))
|
||||
|
||||
@@ -58,6 +67,88 @@ export function ResourceContent({ workspaceId, resource, previewMode }: Resource
|
||||
}
|
||||
}
|
||||
|
||||
interface EmbeddedWorkflowActionsProps {
|
||||
workspaceId: string
|
||||
workflowId: string
|
||||
}
|
||||
|
||||
export function EmbeddedWorkflowActions({
|
||||
workspaceId,
|
||||
workflowId,
|
||||
}: EmbeddedWorkflowActionsProps) {
|
||||
const router = useRouter()
|
||||
const { navigateToSettings } = useSettingsNavigation()
|
||||
const { userPermissions: effectivePermissions } = useWorkspacePermissionsContext()
|
||||
const setActiveWorkflow = useWorkflowRegistry((state) => state.setActiveWorkflow)
|
||||
const { handleRunWorkflow, handleCancelExecution } = useWorkflowExecution()
|
||||
const { isExecuting } = useCurrentWorkflowExecution()
|
||||
const { usageExceeded } = useUsageLimits()
|
||||
|
||||
useEffect(() => {
|
||||
setActiveWorkflow(workflowId)
|
||||
}, [setActiveWorkflow, workflowId])
|
||||
|
||||
const isRunButtonDisabled = !isExecuting && (!effectivePermissions.canRead && !effectivePermissions.isLoading)
|
||||
|
||||
const handleRun = useCallback(async () => {
|
||||
if (isExecuting) {
|
||||
await handleCancelExecution()
|
||||
return
|
||||
}
|
||||
|
||||
if (usageExceeded) {
|
||||
navigateToSettings({ section: 'subscription' })
|
||||
return
|
||||
}
|
||||
|
||||
await handleRunWorkflow()
|
||||
}, [handleCancelExecution, handleRunWorkflow, isExecuting, navigateToSettings, usageExceeded])
|
||||
|
||||
const handleOpenWorkflow = useCallback(() => {
|
||||
router.push(`/workspace/${workspaceId}/w/${workflowId}`)
|
||||
}, [router, workspaceId, workflowId])
|
||||
|
||||
return (
|
||||
<>
|
||||
<Tooltip.Root>
|
||||
<Tooltip.Trigger asChild>
|
||||
<Button
|
||||
variant='subtle'
|
||||
onClick={handleOpenWorkflow}
|
||||
className='shrink-0 bg-transparent px-[8px] py-[5px] text-[12px]'
|
||||
aria-label='Open workflow'
|
||||
>
|
||||
<WorkflowIcon className='h-[16px] w-[16px] text-[var(--text-icon)]' />
|
||||
</Button>
|
||||
</Tooltip.Trigger>
|
||||
<Tooltip.Content side='bottom'>
|
||||
<p>Open Workflow</p>
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Root>
|
||||
<Tooltip.Root>
|
||||
<Tooltip.Trigger asChild>
|
||||
<Button
|
||||
variant='subtle'
|
||||
onClick={() => void handleRun()}
|
||||
disabled={isRunButtonDisabled}
|
||||
className='shrink-0 bg-transparent px-[8px] py-[5px] text-[12px]'
|
||||
aria-label={isExecuting ? 'Stop workflow' : 'Run workflow'}
|
||||
>
|
||||
{isExecuting ? (
|
||||
<Square className='h-[16px] w-[16px] text-[var(--text-icon)]' />
|
||||
) : (
|
||||
<PlayOutline className='h-[16px] w-[16px] text-[var(--text-icon)]' />
|
||||
)}
|
||||
</Button>
|
||||
</Tooltip.Trigger>
|
||||
<Tooltip.Content side='bottom'>
|
||||
<p>{isExecuting ? 'Stop' : 'Run'}</p>
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Root>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
interface EmbeddedFileProps {
|
||||
workspaceId: string
|
||||
fileId: string
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use client'
|
||||
|
||||
import type { ElementType, SVGProps } from 'react'
|
||||
import type { ElementType, ReactNode, SVGProps } from 'react'
|
||||
import { Button, Tooltip } from '@/components/emcn'
|
||||
import { PanelLeft, Table as TableIcon } from '@/components/emcn/icons'
|
||||
import { WorkflowIcon } from '@/components/icons'
|
||||
@@ -48,6 +48,7 @@ interface ResourceTabsProps {
|
||||
onCollapse: () => void
|
||||
previewMode?: PreviewMode
|
||||
onCyclePreviewMode?: () => void
|
||||
actions?: ReactNode
|
||||
}
|
||||
|
||||
const RESOURCE_ICONS: Record<Exclude<MothershipResourceType, 'file'>, ElementType> = {
|
||||
@@ -73,6 +74,7 @@ export function ResourceTabs({
|
||||
onCollapse,
|
||||
previewMode,
|
||||
onCyclePreviewMode,
|
||||
actions,
|
||||
}: ResourceTabsProps) {
|
||||
return (
|
||||
<div className='flex shrink-0 items-center border-[var(--border)] border-b px-[16px] py-[8.5px]'>
|
||||
@@ -118,26 +120,29 @@ export function ResourceTabs({
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
{previewMode && onCyclePreviewMode && (
|
||||
<Tooltip.Root>
|
||||
<Tooltip.Trigger asChild>
|
||||
<Button
|
||||
variant='subtle'
|
||||
onClick={onCyclePreviewMode}
|
||||
className='ml-auto shrink-0 bg-transparent px-[8px] py-[5px] text-[12px]'
|
||||
aria-label='Cycle preview mode'
|
||||
>
|
||||
<PreviewModeIcon
|
||||
mode={previewMode}
|
||||
className='h-[16px] w-[16px] text-[var(--text-icon)]'
|
||||
/>
|
||||
</Button>
|
||||
</Tooltip.Trigger>
|
||||
<Tooltip.Content side='bottom'>
|
||||
<p>Preview mode</p>
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Root>
|
||||
)}
|
||||
<div className='ml-auto flex shrink-0 items-center gap-[6px]'>
|
||||
{actions}
|
||||
{previewMode && onCyclePreviewMode && (
|
||||
<Tooltip.Root>
|
||||
<Tooltip.Trigger asChild>
|
||||
<Button
|
||||
variant='subtle'
|
||||
onClick={onCyclePreviewMode}
|
||||
className='shrink-0 bg-transparent px-[8px] py-[5px] text-[12px]'
|
||||
aria-label='Cycle preview mode'
|
||||
>
|
||||
<PreviewModeIcon
|
||||
mode={previewMode}
|
||||
className='h-[16px] w-[16px] text-[var(--text-icon)]'
|
||||
/>
|
||||
</Button>
|
||||
</Tooltip.Trigger>
|
||||
<Tooltip.Content side='bottom'>
|
||||
<p>Preview mode</p>
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Root>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ 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 type { MothershipResource } from '@/app/workspace/[workspaceId]/home/types'
|
||||
import { ResourceContent, ResourceTabs } from './components'
|
||||
import { EmbeddedWorkflowActions, ResourceContent, ResourceTabs } from './components'
|
||||
|
||||
const PREVIEWABLE_EXTENSIONS = new Set(['md', 'html', 'htm', 'csv'])
|
||||
const PREVIEW_ONLY_EXTENSIONS = new Set(['html', 'htm'])
|
||||
@@ -53,6 +53,11 @@ export function MothershipView({
|
||||
const isActivePreviewable =
|
||||
active?.type === 'file' && PREVIEWABLE_EXTENSIONS.has(getFileExtension(active.title))
|
||||
|
||||
const headerActions =
|
||||
active?.type === 'workflow' ? (
|
||||
<EmbeddedWorkflowActions workspaceId={workspaceId} workflowId={active.id} />
|
||||
) : null
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
@@ -67,6 +72,7 @@ export function MothershipView({
|
||||
activeId={active?.id ?? null}
|
||||
onSelect={onSelectResource}
|
||||
onCollapse={onCollapse}
|
||||
actions={headerActions}
|
||||
previewMode={isActivePreviewable ? previewMode : undefined}
|
||||
onCyclePreviewMode={isActivePreviewable ? handleCyclePreview : undefined}
|
||||
/>
|
||||
|
||||
@@ -379,7 +379,8 @@ const WorkflowContent = React.memo(
|
||||
|
||||
const showTrainingModal = useCopilotTrainingStore((state) => state.showModal)
|
||||
|
||||
const { handleRunFromBlock, handleRunUntilBlock } = useWorkflowExecution()
|
||||
const { handleRunFromBlock, handleRunUntilBlock, handleRunWorkflow, handleCancelExecution } =
|
||||
useWorkflowExecution()
|
||||
|
||||
const snapToGridSize = useSnapToGridSize()
|
||||
const snapToGrid = snapToGridSize > 0
|
||||
@@ -612,7 +613,6 @@ const WorkflowContent = React.memo(
|
||||
|
||||
const { userPermissions, workspacePermissions, permissionsError } =
|
||||
useWorkspacePermissionsContext()
|
||||
|
||||
/** Returns read-only permissions when viewing snapshot, otherwise user permissions. */
|
||||
const effectivePermissions = useMemo(() => {
|
||||
if (currentWorkflow.isSnapshotView) {
|
||||
@@ -625,7 +625,6 @@ const WorkflowContent = React.memo(
|
||||
}
|
||||
return userPermissions
|
||||
}, [userPermissions, currentWorkflow.isSnapshotView])
|
||||
|
||||
const {
|
||||
collaborativeBatchAddEdges,
|
||||
collaborativeBatchRemoveEdges,
|
||||
@@ -3836,9 +3835,9 @@ const WorkflowContent = React.memo(
|
||||
edges={edgesWithSelection}
|
||||
onNodesChange={onNodesChange}
|
||||
onEdgesChange={onEdgesChange}
|
||||
onConnect={effectivePermissions.canEdit ? onConnect : undefined}
|
||||
onConnectStart={effectivePermissions.canEdit ? onConnectStart : undefined}
|
||||
onConnectEnd={effectivePermissions.canEdit ? onConnectEnd : undefined}
|
||||
onConnect={!embedded && effectivePermissions.canEdit ? onConnect : undefined}
|
||||
onConnectStart={!embedded && effectivePermissions.canEdit ? onConnectStart : undefined}
|
||||
onConnectEnd={!embedded && effectivePermissions.canEdit ? onConnectEnd : undefined}
|
||||
nodeTypes={nodeTypes}
|
||||
edgeTypes={edgeTypes}
|
||||
onMouseDown={handleCanvasMouseDown}
|
||||
@@ -3859,7 +3858,7 @@ const WorkflowContent = React.memo(
|
||||
connectionLineStyle={connectionLineStyle}
|
||||
connectionLineType={ConnectionLineType.SmoothStep}
|
||||
onPaneClick={onPaneClick}
|
||||
onEdgeClick={onEdgeClick}
|
||||
onEdgeClick={embedded ? undefined : onEdgeClick}
|
||||
onNodeClick={handleNodeClick}
|
||||
onPaneContextMenu={handlePaneContextMenu}
|
||||
onNodeContextMenu={handleNodeContextMenu}
|
||||
@@ -3876,8 +3875,8 @@ const WorkflowContent = React.memo(
|
||||
nodesDraggable={!embedded && effectivePermissions.canEdit}
|
||||
draggable={false}
|
||||
noWheelClassName='allow-scroll'
|
||||
edgesFocusable={true}
|
||||
edgesUpdatable={effectivePermissions.canEdit}
|
||||
edgesFocusable={!embedded}
|
||||
edgesUpdatable={!embedded && effectivePermissions.canEdit}
|
||||
className={`workflow-container h-full bg-[var(--bg)] transition-opacity duration-150 ${reactFlowStyles} ${isCanvasReady ? 'opacity-100' : 'opacity-0'} ${isHandMode ? 'canvas-mode-hand' : 'canvas-mode-cursor'}`}
|
||||
onNodeDrag={effectivePermissions.canEdit ? onNodeDrag : undefined}
|
||||
onNodeDragStop={effectivePermissions.canEdit ? onNodeDragStop : undefined}
|
||||
@@ -3985,7 +3984,7 @@ const WorkflowContent = React.memo(
|
||||
{!embedded && <DiffControls />}
|
||||
</div>
|
||||
|
||||
{!embedded && <Terminal />}
|
||||
<Terminal />
|
||||
</div>
|
||||
|
||||
{!embedded && <Panel />}
|
||||
|
||||
Reference in New Issue
Block a user