mirror of
https://github.com/simstudioai/sim.git
synced 2026-01-08 22:48:14 -05:00
Added persistence and localization of notifications to each workflow
This commit is contained in:
@@ -115,6 +115,8 @@ function WorkflowCanvas() {
|
||||
} = useWorkflowStore()
|
||||
const { addNotification } = useNotificationStore()
|
||||
|
||||
const params = useParams()
|
||||
|
||||
// Convert blocks to ReactFlow nodes using local selectedBlockId
|
||||
const nodes = Object.values(blocks).map((block) => ({
|
||||
id: block.id,
|
||||
@@ -256,10 +258,6 @@ function WorkflowCanvas() {
|
||||
return () => window.removeEventListener('keydown', handleKeyDown)
|
||||
}, [canUndo, canRedo, undo, redo])
|
||||
|
||||
// useEffect(() => {
|
||||
// addNotification('console', 'Welcome to the workflow editor!')
|
||||
// }, [])
|
||||
|
||||
return (
|
||||
<div className="relative w-full h-[calc(100vh-4rem)]">
|
||||
<NotificationList />
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
import { useNotificationStore } from '@/stores/notifications/notifications-store'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { ErrorIcon } from '@/components/icons'
|
||||
import { useState, useEffect } from 'react'
|
||||
|
||||
interface NotificationDropdownItemProps {
|
||||
id: string
|
||||
@@ -34,6 +35,14 @@ export function NotificationDropdownItem({
|
||||
}: NotificationDropdownItemProps) {
|
||||
const { showNotification } = useNotificationStore()
|
||||
const Icon = NotificationIcon[type]
|
||||
const [, forceUpdate] = useState({})
|
||||
|
||||
// Update the time display every minute
|
||||
useEffect(() => {
|
||||
const interval = setInterval(() => forceUpdate({}), 60000)
|
||||
return () => clearInterval(interval)
|
||||
}, [])
|
||||
|
||||
const timeAgo = formatDistanceToNow(timestamp, { addSuffix: true })
|
||||
|
||||
// Truncate message if it's too long
|
||||
|
||||
@@ -19,13 +19,18 @@ import { useWorkflowRegistry } from '@/stores/workflow/workflow-registry'
|
||||
import { useRouter } from 'next/navigation'
|
||||
|
||||
export function ControlBar() {
|
||||
const { notifications } = useNotificationStore()
|
||||
const { notifications, getWorkflowNotifications } = useNotificationStore()
|
||||
const { history, undo, redo } = useWorkflowStore()
|
||||
const [, forceUpdate] = useState({})
|
||||
const { isExecuting, handleRunWorkflow } = useWorkflowExecution()
|
||||
const { workflows, removeWorkflow, activeWorkflowId } = useWorkflowRegistry()
|
||||
const router = useRouter()
|
||||
|
||||
// Get notifications for current workflow
|
||||
const workflowNotifications = activeWorkflowId
|
||||
? getWorkflowNotifications(activeWorkflowId)
|
||||
: notifications // Show all if no workflow is active
|
||||
|
||||
const handleDeleteWorkflow = () => {
|
||||
if (!activeWorkflowId) return
|
||||
const newWorkflows = { ...workflows }
|
||||
@@ -120,7 +125,7 @@ export function ControlBar() {
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
|
||||
{notifications.length === 0 ? (
|
||||
{workflowNotifications.length === 0 ? (
|
||||
<DropdownMenuContent align="end" className="w-40">
|
||||
<DropdownMenuItem className="text-sm text-muted-foreground">
|
||||
No new notifications
|
||||
@@ -128,15 +133,12 @@ export function ControlBar() {
|
||||
</DropdownMenuContent>
|
||||
) : (
|
||||
<DropdownMenuContent align="end" className="w-60">
|
||||
{[...notifications]
|
||||
{[...workflowNotifications]
|
||||
.sort((a, b) => b.timestamp - a.timestamp)
|
||||
.map((notification) => (
|
||||
<NotificationDropdownItem
|
||||
id={notification.id}
|
||||
key={notification.id}
|
||||
type={notification.type}
|
||||
message={notification.message}
|
||||
timestamp={notification.timestamp}
|
||||
{...notification}
|
||||
/>
|
||||
))}
|
||||
</DropdownMenuContent>
|
||||
|
||||
@@ -4,11 +4,13 @@ import { Serializer } from '@/serializer'
|
||||
import { Executor } from '@/executor'
|
||||
import { ExecutionResult } from '@/executor/types'
|
||||
import { useNotificationStore } from '@/stores/notifications/notifications-store'
|
||||
import { useWorkflowRegistry } from '@/stores/workflow/workflow-registry'
|
||||
|
||||
export function useWorkflowExecution() {
|
||||
const [isExecuting, setIsExecuting] = useState(false)
|
||||
const [executionResult, setExecutionResult] = useState<ExecutionResult | null>(null)
|
||||
const { blocks, edges } = useWorkflowStore()
|
||||
const { activeWorkflowId } = useWorkflowRegistry()
|
||||
const { addNotification } = useNotificationStore()
|
||||
|
||||
const handleRunWorkflow = useCallback(async () => {
|
||||
@@ -31,12 +33,13 @@ export function useWorkflowExecution() {
|
||||
const result = await executor.execute(crypto.randomUUID())
|
||||
setExecutionResult(result)
|
||||
|
||||
// Show execution result
|
||||
// Show execution result with workflowId
|
||||
addNotification(
|
||||
result.success ? 'console' : 'error',
|
||||
result.success
|
||||
? 'Workflow completed successfully'
|
||||
: `Failed to execute workflow: ${result.error}`
|
||||
: `Failed to execute workflow: ${result.error}`,
|
||||
activeWorkflowId
|
||||
)
|
||||
|
||||
// Log detailed result to console
|
||||
@@ -58,11 +61,11 @@ export function useWorkflowExecution() {
|
||||
data: {},
|
||||
error: errorMessage
|
||||
})
|
||||
addNotification('error', `Failed to execute workflow: ${errorMessage}`)
|
||||
addNotification('error', `Failed to execute workflow: ${errorMessage}`, activeWorkflowId)
|
||||
} finally {
|
||||
setIsExecuting(false)
|
||||
}
|
||||
}, [blocks, edges, addNotification])
|
||||
}, [blocks, edges, addNotification, activeWorkflowId])
|
||||
|
||||
return { isExecuting, executionResult, handleRunWorkflow }
|
||||
}
|
||||
@@ -3,39 +3,75 @@ import { devtools } from 'zustand/middleware'
|
||||
import { NotificationType, Notification, NotificationStore } from './types'
|
||||
import { getTimestamp } from '@/lib/utils'
|
||||
|
||||
const STORAGE_KEY = 'workflow-notifications'
|
||||
|
||||
// Helper to load persisted notifications
|
||||
const loadPersistedNotifications = (): Notification[] => {
|
||||
if (typeof window === 'undefined') return []
|
||||
const saved = localStorage.getItem(STORAGE_KEY)
|
||||
return saved ? JSON.parse(saved) : []
|
||||
}
|
||||
|
||||
// Helper to save notifications to localStorage
|
||||
const persistNotifications = (notifications: Notification[]) => {
|
||||
if (typeof window === 'undefined') return
|
||||
localStorage.setItem(STORAGE_KEY, JSON.stringify(notifications))
|
||||
}
|
||||
|
||||
export const useNotificationStore = create<NotificationStore>()(
|
||||
devtools(
|
||||
(set) => ({
|
||||
notifications: [],
|
||||
addNotification: (type, message) => {
|
||||
(set, get) => ({
|
||||
notifications: loadPersistedNotifications(),
|
||||
|
||||
addNotification: (type, message, workflowId) => {
|
||||
const notification: Notification = {
|
||||
id: typeof window === 'undefined' ? '1' : crypto.randomUUID(),
|
||||
type,
|
||||
message,
|
||||
timestamp: getTimestamp(),
|
||||
isVisible: true,
|
||||
workflowId
|
||||
}
|
||||
set((state) => ({
|
||||
notifications: [...state.notifications, notification],
|
||||
}))
|
||||
set((state) => {
|
||||
const newNotifications = [...state.notifications, notification]
|
||||
persistNotifications(newNotifications)
|
||||
return { notifications: newNotifications }
|
||||
})
|
||||
},
|
||||
|
||||
hideNotification: (id) =>
|
||||
set((state) => ({
|
||||
notifications: state.notifications.map((n) =>
|
||||
set((state) => {
|
||||
const newNotifications = state.notifications.map((n) =>
|
||||
n.id === id ? { ...n, isVisible: false } : n
|
||||
),
|
||||
})),
|
||||
)
|
||||
persistNotifications(newNotifications)
|
||||
return { notifications: newNotifications }
|
||||
}),
|
||||
|
||||
showNotification: (id) =>
|
||||
set((state) => ({
|
||||
notifications: state.notifications.map((n) =>
|
||||
set((state) => {
|
||||
const newNotifications = state.notifications.map((n) =>
|
||||
n.id === id ? { ...n, isVisible: true } : n
|
||||
),
|
||||
})),
|
||||
)
|
||||
persistNotifications(newNotifications)
|
||||
return { notifications: newNotifications }
|
||||
}),
|
||||
|
||||
removeNotification: (id) =>
|
||||
set((state) => ({
|
||||
notifications: state.notifications.filter((n) => n.id !== id),
|
||||
})),
|
||||
clearNotifications: () => set({ notifications: [] }),
|
||||
set((state) => {
|
||||
const newNotifications = state.notifications.filter((n) => n.id !== id)
|
||||
persistNotifications(newNotifications)
|
||||
return { notifications: newNotifications }
|
||||
}),
|
||||
|
||||
clearNotifications: () => {
|
||||
persistNotifications([])
|
||||
set({ notifications: [] })
|
||||
},
|
||||
|
||||
getWorkflowNotifications: (workflowId) => {
|
||||
return get().notifications.filter(n => n.workflowId === workflowId)
|
||||
}
|
||||
}),
|
||||
{ name: 'notification-store' }
|
||||
)
|
||||
|
||||
@@ -6,13 +6,15 @@ export interface Notification {
|
||||
message: string
|
||||
timestamp: number
|
||||
isVisible: boolean
|
||||
workflowId: string | null
|
||||
}
|
||||
|
||||
export interface NotificationStore {
|
||||
notifications: Notification[]
|
||||
addNotification: (type: NotificationType, message: string) => void
|
||||
addNotification: (type: NotificationType, message: string, workflowId: string | null) => void
|
||||
hideNotification: (id: string) => void
|
||||
showNotification: (id: string) => void
|
||||
removeNotification: (id: string) => void
|
||||
clearNotifications: () => void
|
||||
getWorkflowNotifications: (workflowId: string) => Notification[]
|
||||
}
|
||||
Reference in New Issue
Block a user