mirror of
https://github.com/simstudioai/sim.git
synced 2026-10-28 02:41:36 +08:00
Send log request message after manual workflow run
This commit is contained in:
+19
-2
@@ -8,6 +8,7 @@ import {
|
||||
} from '@/app/workspace/[workspaceId]/files/components/file-viewer'
|
||||
import type { MothershipResource } from '@/app/workspace/[workspaceId]/home/types'
|
||||
import { Table } from '@/app/workspace/[workspaceId]/tables/[tableId]/components'
|
||||
import type { ExecutionResult } from '@/executor/types'
|
||||
import { useWorkspaceFiles } from '@/hooks/queries/workspace-files'
|
||||
|
||||
const Workflow = lazy(() => import('@/app/workspace/[workspaceId]/w/[workflowId]/workflow'))
|
||||
@@ -24,6 +25,7 @@ interface ResourceContentProps {
|
||||
workspaceId: string
|
||||
resource: MothershipResource
|
||||
previewMode?: PreviewMode
|
||||
onWorkflowRunComplete?: (workflowName: string, result: ExecutionResult) => Promise<void>
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -31,7 +33,12 @@ interface ResourceContentProps {
|
||||
* Handles table, file, and workflow resource types with appropriate
|
||||
* embedded rendering for each.
|
||||
*/
|
||||
export function ResourceContent({ workspaceId, resource, previewMode }: ResourceContentProps) {
|
||||
export function ResourceContent({
|
||||
workspaceId,
|
||||
resource,
|
||||
previewMode,
|
||||
onWorkflowRunComplete,
|
||||
}: ResourceContentProps) {
|
||||
switch (resource.type) {
|
||||
case 'table':
|
||||
return <Table key={resource.id} workspaceId={workspaceId} tableId={resource.id} embedded />
|
||||
@@ -49,7 +56,17 @@ export function ResourceContent({ workspaceId, resource, previewMode }: Resource
|
||||
case 'workflow':
|
||||
return (
|
||||
<Suspense fallback={LOADING_SKELETON}>
|
||||
<Workflow key={resource.id} workspaceId={workspaceId} workflowId={resource.id} embedded />
|
||||
<Workflow
|
||||
key={resource.id}
|
||||
workspaceId={workspaceId}
|
||||
workflowId={resource.id}
|
||||
embedded
|
||||
onManualRunComplete={
|
||||
onWorkflowRunComplete
|
||||
? (result) => onWorkflowRunComplete(resource.title, result)
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
</Suspense>
|
||||
)
|
||||
|
||||
|
||||
+4
@@ -5,6 +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 type { ExecutionResult } from '@/executor/types'
|
||||
import { ResourceContent, ResourceTabs } from './components'
|
||||
|
||||
const PREVIEWABLE_EXTENSIONS = new Set(['md', 'html', 'htm', 'csv'])
|
||||
@@ -21,6 +22,7 @@ interface MothershipViewProps {
|
||||
resources: MothershipResource[]
|
||||
activeResourceId: string | null
|
||||
onSelectResource: (id: string) => void
|
||||
onWorkflowRunComplete?: (workflowName: string, result: ExecutionResult) => Promise<void>
|
||||
onCollapse: () => void
|
||||
isCollapsed: boolean
|
||||
className?: string
|
||||
@@ -36,6 +38,7 @@ export function MothershipView({
|
||||
resources,
|
||||
activeResourceId,
|
||||
onSelectResource,
|
||||
onWorkflowRunComplete,
|
||||
onCollapse,
|
||||
isCollapsed,
|
||||
className,
|
||||
@@ -76,6 +79,7 @@ export function MothershipView({
|
||||
workspaceId={workspaceId}
|
||||
resource={active}
|
||||
previewMode={isActivePreviewable ? previewMode : undefined}
|
||||
onWorkflowRunComplete={onWorkflowRunComplete}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -165,6 +165,7 @@ export function Home({ chatId }: HomeProps = {}) {
|
||||
messages,
|
||||
isSending,
|
||||
sendMessage,
|
||||
sendWorkflowResultToChat,
|
||||
stopGeneration,
|
||||
resources,
|
||||
activeResourceId,
|
||||
@@ -348,6 +349,7 @@ export function Home({ chatId }: HomeProps = {}) {
|
||||
resources={resources}
|
||||
activeResourceId={activeResourceId}
|
||||
onSelectResource={setActiveResourceId}
|
||||
onWorkflowRunComplete={sendWorkflowResultToChat}
|
||||
onCollapse={collapseResource}
|
||||
isCollapsed={isResourceCollapsed}
|
||||
className={animateResourcePanel ? 'animate-slide-in-right' : undefined}
|
||||
|
||||
@@ -3,6 +3,7 @@ import { createLogger } from '@sim/logger'
|
||||
import { useQueryClient } from '@tanstack/react-query'
|
||||
import { usePathname } from 'next/navigation'
|
||||
import { MOTHERSHIP_CHAT_API_PATH } from '@/lib/copilot/constants'
|
||||
import type { ExecutionResult } from '@/executor/types'
|
||||
import { tableKeys } from '@/hooks/queries/tables'
|
||||
import {
|
||||
type TaskChatHistory,
|
||||
@@ -40,6 +41,7 @@ export interface UseChatReturn {
|
||||
isSending: boolean
|
||||
error: string | null
|
||||
sendMessage: (message: string, fileAttachments?: FileAttachmentForApi[]) => Promise<void>
|
||||
sendWorkflowResultToChat: (workflowName: string, result: ExecutionResult) => Promise<void>
|
||||
stopGeneration: () => Promise<void>
|
||||
resources: MothershipResource[]
|
||||
activeResourceId: string | null
|
||||
@@ -51,6 +53,10 @@ const STATE_TO_STATUS: Record<string, ToolCallStatus> = {
|
||||
error: 'error',
|
||||
} as const
|
||||
|
||||
function formatWorkflowResultMessage(_workflowName: string, _result: ExecutionResult): string {
|
||||
return 'I ran the workflow, please check the logs'
|
||||
}
|
||||
|
||||
function mapStoredBlock(block: TaskStoredContentBlock): ContentBlock {
|
||||
const mapped: ContentBlock = {
|
||||
type: block.type as ContentBlockType,
|
||||
@@ -146,6 +152,7 @@ export function useChat(workspaceId: string, initialChatId?: string): UseChatRet
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
const [resources, setResources] = useState<MothershipResource[]>([])
|
||||
const [activeResourceId, setActiveResourceId] = useState<string | null>(null)
|
||||
const [queuedMessages, setQueuedMessages] = useState<string[]>([])
|
||||
const abortControllerRef = useRef<AbortController | null>(null)
|
||||
const chatIdRef = useRef<string | undefined>(initialChatId)
|
||||
const appliedChatIdRef = useRef<string | undefined>(undefined)
|
||||
@@ -682,6 +689,28 @@ export function useChat(workspaceId: string, initialChatId?: string): UseChatRet
|
||||
[workspaceId, queryClient, processSSEStream, finalize, persistPartialResponse]
|
||||
)
|
||||
|
||||
const sendWorkflowResultToChat = useCallback(
|
||||
async (workflowName: string, result: ExecutionResult) => {
|
||||
const message = formatWorkflowResultMessage(workflowName, result)
|
||||
|
||||
if (sendingRef.current || isSending) {
|
||||
setQueuedMessages((prev) => [...prev, message])
|
||||
return
|
||||
}
|
||||
|
||||
await sendMessage(message)
|
||||
},
|
||||
[isSending, sendMessage]
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
if (isSending || queuedMessages.length === 0) return
|
||||
|
||||
const [nextMessage, ...rest] = queuedMessages
|
||||
setQueuedMessages(rest)
|
||||
void sendMessage(nextMessage)
|
||||
}, [isSending, queuedMessages, sendMessage])
|
||||
|
||||
const stopGeneration = useCallback(async () => {
|
||||
if (sendingRef.current) {
|
||||
await persistPartialResponse()
|
||||
@@ -719,6 +748,7 @@ export function useChat(workspaceId: string, initialChatId?: string): UseChatRet
|
||||
isSending,
|
||||
error,
|
||||
sendMessage,
|
||||
sendWorkflowResultToChat,
|
||||
stopGeneration,
|
||||
resources,
|
||||
activeResourceId,
|
||||
|
||||
@@ -39,6 +39,7 @@ import { CanvasMenu } from '@/app/workspace/[workspaceId]/w/[workflowId]/compone
|
||||
import { Cursors } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/cursors/cursors'
|
||||
import { ErrorBoundary } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/error/index'
|
||||
import { NoteBlock } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/note-block/note-block'
|
||||
import { useUsageLimits } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/hooks'
|
||||
import type { SubflowNodeData } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/subflows/subflow-node'
|
||||
import { TrainingModal } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/training-modal/training-modal'
|
||||
import { WorkflowBlock } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/workflow-block'
|
||||
@@ -71,6 +72,7 @@ import {
|
||||
import { useSocket } from '@/app/workspace/providers/socket-provider'
|
||||
import { getBlock } from '@/blocks'
|
||||
import { isAnnotationOnlyBlock } from '@/executor/constants'
|
||||
import type { ExecutionResult } from '@/executor/types'
|
||||
import { useWorkspaceEnvironment } from '@/hooks/queries/environment'
|
||||
import { useAutoConnect, useSnapToGridSize } from '@/hooks/queries/general-settings'
|
||||
import { useCanvasViewport } from '@/hooks/use-canvas-viewport'
|
||||
@@ -91,7 +93,6 @@ import { useWorkflowRegistry } from '@/stores/workflows/registry/store'
|
||||
import { getUniqueBlockName, prepareBlockState } from '@/stores/workflows/utils'
|
||||
import { useWorkflowStore } from '@/stores/workflows/workflow/store'
|
||||
import type { BlockState } from '@/stores/workflows/workflow/types'
|
||||
import { useUsageLimits } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/hooks'
|
||||
|
||||
/** Lazy-loaded components for non-critical UI that can load after initial render */
|
||||
const LazyChat = lazy(() =>
|
||||
@@ -234,6 +235,7 @@ interface WorkflowContentProps {
|
||||
workspaceId?: string
|
||||
workflowId?: string
|
||||
embedded?: boolean
|
||||
onManualRunComplete?: (result: ExecutionResult) => Promise<void> | void
|
||||
}
|
||||
|
||||
const WorkflowContent = React.memo(
|
||||
@@ -241,6 +243,7 @@ const WorkflowContent = React.memo(
|
||||
workspaceId: propWorkspaceId,
|
||||
workflowId: propWorkflowId,
|
||||
embedded,
|
||||
onManualRunComplete,
|
||||
}: WorkflowContentProps = {}) => {
|
||||
const [isCanvasReady, setIsCanvasReady] = useState(false)
|
||||
const [potentialParentId, setPotentialParentId] = useState<string | null>(null)
|
||||
@@ -859,7 +862,7 @@ const WorkflowContent = React.memo(
|
||||
const getLastExecutionSnapshot = useExecutionStore((s) => s.getLastExecutionSnapshot)
|
||||
|
||||
const canRunWorkflow = effectivePermissions.canRead
|
||||
const isRunButtonDisabled = !isExecuting && (!canRunWorkflow && !effectivePermissions.isLoading)
|
||||
const isRunButtonDisabled = !isExecuting && !canRunWorkflow && !effectivePermissions.isLoading
|
||||
|
||||
const openSubscriptionSettings = useCallback(() => {
|
||||
navigateToSettings({ section: 'subscription' })
|
||||
@@ -876,11 +879,27 @@ const WorkflowContent = React.memo(
|
||||
return
|
||||
}
|
||||
|
||||
await handleRunWorkflow()
|
||||
const result = await handleRunWorkflow()
|
||||
|
||||
if (
|
||||
!onManualRunComplete ||
|
||||
!result ||
|
||||
!('success' in result) ||
|
||||
'stream' in result ||
|
||||
result.status === 'cancelled' ||
|
||||
result.status === 'paused'
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
Promise.resolve(onManualRunComplete(result)).catch((error) => {
|
||||
logger.error('Failed to send embedded workflow result to mothership chat', { error })
|
||||
})
|
||||
}, [
|
||||
handleCancelExecution,
|
||||
handleRunWorkflow,
|
||||
isExecuting,
|
||||
onManualRunComplete,
|
||||
openSubscriptionSettings,
|
||||
usageExceeded,
|
||||
])
|
||||
@@ -3873,9 +3892,9 @@ const WorkflowContent = React.memo(
|
||||
{showTrainingModal && <TrainingModal />}
|
||||
|
||||
{embedded && (
|
||||
<div className='absolute bottom-[28px] left-1/2 z-[31] flex -translate-x-1/2 items-center gap-[2px] rounded-[10px] border border-[var(--border)] bg-[var(--surface-3)] p-[2px] shadow-sm backdrop-blur-sm'>
|
||||
<div className='-translate-x-1/2 absolute bottom-[28px] left-1/2 z-[31] flex items-center gap-[2px] rounded-[10px] border border-[var(--border)] bg-[var(--surface-3)] p-[2px] shadow-sm backdrop-blur-sm'>
|
||||
<Button
|
||||
className='h-[30px] rounded-[8px] gap-[8px] px-[12px] text-[var(--text-secondary)] hover:bg-[var(--surface-4)] hover:text-[var(--text-primary)]'
|
||||
className='h-[30px] gap-[8px] rounded-[8px] px-[12px] text-[var(--text-secondary)] hover:bg-[var(--surface-4)] hover:text-[var(--text-primary)]'
|
||||
variant='ghost'
|
||||
onClick={() => void handleEmbeddedRun()}
|
||||
disabled={!isExecuting && isRunButtonDisabled}
|
||||
@@ -3888,7 +3907,7 @@ const WorkflowContent = React.memo(
|
||||
{isExecuting ? 'Stop' : 'Run'}
|
||||
</Button>
|
||||
<Button
|
||||
className='h-[30px] rounded-[8px] gap-[8px] px-[12px] text-[var(--text-secondary)] hover:bg-[var(--surface-4)] hover:text-[var(--text-primary)]'
|
||||
className='h-[30px] gap-[8px] rounded-[8px] px-[12px] text-[var(--text-secondary)] hover:bg-[var(--surface-4)] hover:text-[var(--text-primary)]'
|
||||
variant='ghost'
|
||||
onClick={handleOpenFullWorkflow}
|
||||
>
|
||||
@@ -4081,18 +4100,26 @@ interface WorkflowProps {
|
||||
workspaceId?: string
|
||||
workflowId?: string
|
||||
embedded?: boolean
|
||||
onManualRunComplete?: (result: ExecutionResult) => Promise<void> | void
|
||||
}
|
||||
|
||||
/** Workflow page with ReactFlowProvider and error boundary wrapper. */
|
||||
const Workflow = React.memo(({ workspaceId, workflowId, embedded }: WorkflowProps = {}) => {
|
||||
return (
|
||||
<ReactFlowProvider>
|
||||
<ErrorBoundary>
|
||||
<WorkflowContent workspaceId={workspaceId} workflowId={workflowId} embedded={embedded} />
|
||||
</ErrorBoundary>
|
||||
</ReactFlowProvider>
|
||||
)
|
||||
})
|
||||
const Workflow = React.memo(
|
||||
({ workspaceId, workflowId, embedded, onManualRunComplete }: WorkflowProps = {}) => {
|
||||
return (
|
||||
<ReactFlowProvider>
|
||||
<ErrorBoundary>
|
||||
<WorkflowContent
|
||||
workspaceId={workspaceId}
|
||||
workflowId={workflowId}
|
||||
embedded={embedded}
|
||||
onManualRunComplete={onManualRunComplete}
|
||||
/>
|
||||
</ErrorBoundary>
|
||||
</ReactFlowProvider>
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
Workflow.displayName = 'Workflow'
|
||||
|
||||
|
||||
Reference in New Issue
Block a user