fix(ui) add embedded workflow notifications, switch tab on workflow run (#3618)

* Include notification view in embedded workflow view

* fix(ui) fix workflow not showing up when mothership calls run

* Wire up fix in mothership

* Refresh events after workflow run

* Fix so run workflow switches tabs as well

---------

Co-authored-by: Theodore Li <theo@sim.ai>
This commit is contained in:
Theodore Li
2026-03-16 20:22:31 -07:00
committed by GitHub
parent c867801988
commit 974cc66b0e
6 changed files with 56 additions and 6 deletions

View File

@@ -250,6 +250,15 @@ export function Home({ chatId }: HomeProps = {}) {
[sendMessage]
)
useEffect(() => {
const handler = (e: Event) => {
const message = (e as CustomEvent<{ message: string }>).detail?.message
if (message) sendMessage(message)
}
window.addEventListener('mothership-send-message', handler)
return () => window.removeEventListener('mothership-send-message', handler)
}, [sendMessage])
const handleContextAdd = useCallback(
(context: ChatContext) => {
let resourceType: MothershipResourceType | null = null

View File

@@ -651,6 +651,22 @@ export function useChat(
) {
clientExecutionStarted.add(id)
const args = data?.arguments ?? data?.input ?? {}
const targetWorkflowId =
typeof (args as Record<string, unknown>).workflowId === 'string'
? ((args as Record<string, unknown>).workflowId as string)
: useWorkflowRegistry.getState().activeWorkflowId
if (targetWorkflowId) {
const meta = useWorkflowRegistry.getState().workflows[targetWorkflowId]
const wasAdded = addResource({
type: 'workflow',
id: targetWorkflowId,
title: meta?.name ?? 'Workflow',
})
if (!wasAdded && activeResourceIdRef.current !== targetWorkflowId) {
setActiveResourceId(targetWorkflowId)
}
onResourceEventRef.current?.()
}
executeRunToolOnClient(id, name, args as Record<string, unknown>)
}
break

View File

@@ -9,6 +9,7 @@ import {
type Notification,
type NotificationAction,
openCopilotWithMessage,
sendMothershipMessage,
useNotificationStore,
} from '@/stores/notifications'
import { useWorkflowRegistry } from '@/stores/workflows/registry/store'
@@ -81,7 +82,11 @@ function CountdownRing({ onPause }: { onPause: () => void }) {
* Workflow error notifications auto-dismiss after {@link AUTO_DISMISS_MS}ms with a countdown
* ring. Clicking the ring pauses all timers until the notification stack clears.
*/
export const Notifications = memo(function Notifications() {
interface NotificationsProps {
embedded?: boolean
}
export const Notifications = memo(function Notifications({ embedded }: NotificationsProps) {
const activeWorkflowId = useWorkflowRegistry((state) => state.activeWorkflowId)
const allNotifications = useNotificationStore((state) => state.notifications)
@@ -112,7 +117,11 @@ export const Notifications = memo(function Notifications() {
switch (action.type) {
case 'copilot':
openCopilotWithMessage(action.message)
if (embedded) {
sendMothershipMessage(action.message)
} else {
openCopilotWithMessage(action.message)
}
break
case 'refresh':
window.location.reload()
@@ -133,7 +142,7 @@ export const Notifications = memo(function Notifications() {
})
}
},
[removeNotification]
[embedded, removeNotification]
)
useRegisterGlobalCommands(() =>
@@ -281,7 +290,9 @@ export const Notifications = memo(function Notifications() {
onClick={() => executeAction(notification.id, notification.action!)}
className='w-full rounded-[5px] px-[8px] py-[4px] font-medium text-[12px]'
>
{ACTION_LABELS[notification.action!.type] ?? 'Take action'}
{embedded && notification.action!.type === 'copilot'
? 'Fix in Mothership'
: (ACTION_LABELS[notification.action!.type] ?? 'Take action')}
</Button>
)}
</div>

View File

@@ -3922,7 +3922,7 @@ const WorkflowContent = React.memo(
</>
)}
{!embedded && <Notifications />}
<Notifications embedded={embedded} />
{!embedded && isWorkflowReady && isWorkflowEmpty && effectivePermissions.canEdit && (
<CommandList />

View File

@@ -4,4 +4,4 @@ export type {
Notification,
NotificationAction,
} from './types'
export { openCopilotWithMessage } from './utils'
export { openCopilotWithMessage, sendMothershipMessage } from './utils'

View File

@@ -3,6 +3,20 @@ import { useCopilotStore, usePanelStore } from '@/stores/panel'
const logger = createLogger('NotificationUtils')
/**
* Dispatches a message to the mothership chat via a custom window event.
* The mothership `Home` component listens for this event and calls `sendMessage`.
*/
export function sendMothershipMessage(message: string): void {
const trimmed = message.trim()
if (!trimmed) {
logger.warn('sendMothershipMessage called with empty message')
return
}
window.dispatchEvent(new CustomEvent('mothership-send-message', { detail: { message: trimmed } }))
logger.info('Dispatched mothership message event', { messageLength: trimmed.length })
}
/**
* Opens the copilot panel and directly sends the message.
*