mirror of
https://github.com/simstudioai/sim.git
synced 2026-04-06 03:00:16 -04:00
feat(readme): added deepwiki to readme, consolidated utils (#2856)
* feat(readme): added deepwiki to readme, consolidated utils * standardized all modals * updated modal copy * standardized modals * streamlined all error msg patterns
This commit is contained in:
@@ -10,7 +10,7 @@
|
||||
<a href="https://sim.ai" target="_blank" rel="noopener noreferrer"><img src="https://img.shields.io/badge/sim.ai-6F3DFA" alt="Sim.ai"></a>
|
||||
<a href="https://discord.gg/Hr4UWYEcTT" target="_blank" rel="noopener noreferrer"><img src="https://img.shields.io/badge/Discord-Join%20Server-5865F2?logo=discord&logoColor=white" alt="Discord"></a>
|
||||
<a href="https://x.com/simdotai" target="_blank" rel="noopener noreferrer"><img src="https://img.shields.io/twitter/follow/simstudioai?style=social" alt="Twitter"></a>
|
||||
<a href="https://docs.sim.ai" target="_blank" rel="noopener noreferrer"><img src="https://img.shields.io/badge/Docs-6F3DFA.svg" alt="Documentation"></a>
|
||||
<a href="https://docs.sim.ai" target="_blank" rel="noopener noreferrer"><img src="https://img.shields.io/badge/Docs-6F3DFA.svg" alt="Documentation"></a> <a href="https://deepwiki.com/simstudioai/sim" target="_blank" rel="noopener noreferrer"><img src="https://img.shields.io/badge/DeepWiki-1E90FF.svg" alt="DeepWiki"></a>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
|
||||
@@ -11,6 +11,7 @@ import { preprocessExecution } from '@/lib/execution/preprocessing'
|
||||
import { LoggingSession } from '@/lib/logs/execution/logging-session'
|
||||
import { normalizeInputFormatValue } from '@/lib/workflows/input-format'
|
||||
import { createStreamingResponse } from '@/lib/workflows/streaming/streaming'
|
||||
import { isValidStartBlockType } from '@/lib/workflows/triggers/start-block-types'
|
||||
import { setFormAuthCookie, validateFormAuth } from '@/app/api/form/utils'
|
||||
import { createErrorResponse, createSuccessResponse } from '@/app/api/workflows/utils'
|
||||
|
||||
@@ -35,10 +36,7 @@ async function getWorkflowInputSchema(workflowId: string): Promise<any[]> {
|
||||
.from(workflowBlocks)
|
||||
.where(eq(workflowBlocks.workflowId, workflowId))
|
||||
|
||||
const startBlock = blocks.find(
|
||||
(block) =>
|
||||
block.type === 'starter' || block.type === 'start_trigger' || block.type === 'input_trigger'
|
||||
)
|
||||
const startBlock = blocks.find((block) => isValidStartBlockType(block.type))
|
||||
|
||||
if (!startBlock) {
|
||||
return []
|
||||
|
||||
@@ -77,7 +77,7 @@ export function DeleteChunkModal({
|
||||
</p>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<Button variant='active' disabled={isDeleting} onClick={onClose}>
|
||||
<Button variant='default' disabled={isDeleting} onClick={onClose}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button variant='destructive' onClick={handleDeleteChunk} disabled={isDeleting}>
|
||||
|
||||
@@ -392,7 +392,7 @@ export function DocumentTagsModal({
|
||||
|
||||
return (
|
||||
<Modal open={open} onOpenChange={handleClose}>
|
||||
<ModalContent>
|
||||
<ModalContent size='sm'>
|
||||
<ModalHeader>
|
||||
<div className='flex items-center justify-between'>
|
||||
<span>Document Tags</span>
|
||||
@@ -486,7 +486,7 @@ export function DocumentTagsModal({
|
||||
/>
|
||||
)}
|
||||
{tagNameConflict && (
|
||||
<span className='text-[11px] text-[var(--text-error)]'>
|
||||
<span className='text-[12px] text-[var(--text-error)]'>
|
||||
A tag with this name already exists
|
||||
</span>
|
||||
)}
|
||||
@@ -639,7 +639,7 @@ export function DocumentTagsModal({
|
||||
/>
|
||||
)}
|
||||
{tagNameConflict && (
|
||||
<span className='text-[11px] text-[var(--text-error)]'>
|
||||
<span className='text-[12px] text-[var(--text-error)]'>
|
||||
A tag with this name already exists
|
||||
</span>
|
||||
)}
|
||||
|
||||
@@ -1161,15 +1161,19 @@ export function Document({
|
||||
<ModalHeader>Delete Document</ModalHeader>
|
||||
<ModalBody>
|
||||
<p className='text-[12px] text-[var(--text-secondary)]'>
|
||||
Are you sure you want to delete "{effectiveDocumentName}"? This will permanently
|
||||
delete the document and all {documentData?.chunkCount ?? 0} chunk
|
||||
Are you sure you want to delete{' '}
|
||||
<span className='font-medium text-[var(--text-primary)]'>
|
||||
{effectiveDocumentName}
|
||||
</span>
|
||||
? This will permanently delete the document and all {documentData?.chunkCount ?? 0}{' '}
|
||||
chunk
|
||||
{documentData?.chunkCount === 1 ? '' : 's'} within it.{' '}
|
||||
<span className='text-[var(--text-error)]'>This action cannot be undone.</span>
|
||||
</p>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<Button
|
||||
variant='active'
|
||||
variant='default'
|
||||
onClick={() => setShowDeleteDocumentDialog(false)}
|
||||
disabled={isDeletingDocument}
|
||||
>
|
||||
|
||||
@@ -1523,15 +1523,16 @@ export function KnowledgeBase({
|
||||
<ModalHeader>Delete Knowledge Base</ModalHeader>
|
||||
<ModalBody>
|
||||
<p className='text-[12px] text-[var(--text-secondary)]'>
|
||||
Are you sure you want to delete "{knowledgeBaseName}"? This will permanently delete
|
||||
the knowledge base and all {pagination.total} document
|
||||
Are you sure you want to delete{' '}
|
||||
<span className='font-medium text-[var(--text-primary)]'>{knowledgeBaseName}</span>?
|
||||
This will permanently delete the knowledge base and all {pagination.total} document
|
||||
{pagination.total === 1 ? '' : 's'} within it.{' '}
|
||||
<span className='text-[var(--text-error)]'>This action cannot be undone.</span>
|
||||
</p>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<Button
|
||||
variant='active'
|
||||
variant='default'
|
||||
onClick={() => setShowDeleteDialog(false)}
|
||||
disabled={isDeleting}
|
||||
>
|
||||
@@ -1549,14 +1550,16 @@ export function KnowledgeBase({
|
||||
<ModalHeader>Delete Document</ModalHeader>
|
||||
<ModalBody>
|
||||
<p className='text-[12px] text-[var(--text-secondary)]'>
|
||||
Are you sure you want to delete "
|
||||
{documents.find((doc) => doc.id === documentToDelete)?.filename ?? 'this document'}"?{' '}
|
||||
<span className='text-[var(--text-error)]'>This action cannot be undone.</span>
|
||||
Are you sure you want to delete{' '}
|
||||
<span className='font-medium text-[var(--text-primary)]'>
|
||||
{documents.find((doc) => doc.id === documentToDelete)?.filename ?? 'this document'}
|
||||
</span>
|
||||
? <span className='text-[var(--text-error)]'>This action cannot be undone.</span>
|
||||
</p>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<Button
|
||||
variant='active'
|
||||
variant='default'
|
||||
onClick={() => {
|
||||
setShowDeleteDocumentModal(false)
|
||||
setDocumentToDelete(null)
|
||||
@@ -1582,7 +1585,7 @@ export function KnowledgeBase({
|
||||
</p>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<Button variant='active' onClick={() => setShowBulkDeleteModal(false)}>
|
||||
<Button variant='default' onClick={() => setShowBulkDeleteModal(false)}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button variant='destructive' onClick={confirmBulkDelete} disabled={isBulkOperating}>
|
||||
|
||||
@@ -221,14 +221,14 @@ export function AddDocumentsModal({
|
||||
|
||||
return (
|
||||
<Modal open={open} onOpenChange={handleClose}>
|
||||
<ModalContent>
|
||||
<ModalContent size='md'>
|
||||
<ModalHeader>Add Documents</ModalHeader>
|
||||
|
||||
<ModalBody>
|
||||
<div className='min-h-0 flex-1 overflow-y-auto'>
|
||||
<div className='space-y-[12px]'>
|
||||
{fileError && (
|
||||
<p className='text-[11px] text-[var(--text-error)] leading-tight'>{fileError}</p>
|
||||
<p className='text-[12px] text-[var(--text-error)] leading-tight'>{fileError}</p>
|
||||
)}
|
||||
|
||||
<div className='flex flex-col gap-[8px]'>
|
||||
@@ -336,7 +336,7 @@ export function AddDocumentsModal({
|
||||
<ModalFooter>
|
||||
<div className='flex w-full items-center justify-between gap-[12px]'>
|
||||
{uploadError ? (
|
||||
<p className='min-w-0 flex-1 truncate text-[11px] text-[var(--text-error)] leading-tight'>
|
||||
<p className='min-w-0 flex-1 truncate text-[12px] text-[var(--text-error)] leading-tight'>
|
||||
{uploadError.message}
|
||||
</p>
|
||||
) : (
|
||||
|
||||
@@ -306,7 +306,7 @@ export function BaseTagsModal({ open, onOpenChange, knowledgeBaseId }: BaseTagsM
|
||||
return (
|
||||
<>
|
||||
<Modal open={open} onOpenChange={handleClose}>
|
||||
<ModalContent>
|
||||
<ModalContent size='sm'>
|
||||
<ModalHeader>
|
||||
<div className='flex items-center justify-between'>
|
||||
<span>Tags</span>
|
||||
@@ -400,7 +400,7 @@ export function BaseTagsModal({ open, onOpenChange, knowledgeBaseId }: BaseTagsM
|
||||
}}
|
||||
/>
|
||||
{tagNameConflict && (
|
||||
<span className='text-[11px] text-[var(--text-error)]'>
|
||||
<span className='text-[12px] text-[var(--text-error)]'>
|
||||
A tag with this name already exists
|
||||
</span>
|
||||
)}
|
||||
@@ -417,7 +417,7 @@ export function BaseTagsModal({ open, onOpenChange, knowledgeBaseId }: BaseTagsM
|
||||
placeholder='Select type'
|
||||
/>
|
||||
{!hasAvailableSlots(createTagForm.fieldType) && (
|
||||
<span className='text-[11px] text-[var(--text-error)]'>
|
||||
<span className='text-[12px] text-[var(--text-error)]'>
|
||||
No available slots for this type. Choose a different type.
|
||||
</span>
|
||||
)}
|
||||
|
||||
@@ -77,7 +77,7 @@ export function RenameDocumentModal({
|
||||
|
||||
return (
|
||||
<Modal open={open} onOpenChange={onOpenChange}>
|
||||
<ModalContent>
|
||||
<ModalContent size='sm'>
|
||||
<ModalHeader>Rename Document</ModalHeader>
|
||||
<form onSubmit={handleSubmit} className='flex min-h-0 flex-1 flex-col'>
|
||||
<ModalBody className='!pb-[16px]'>
|
||||
@@ -108,7 +108,7 @@ export function RenameDocumentModal({
|
||||
<ModalFooter>
|
||||
<div className='flex w-full items-center justify-between gap-[12px]'>
|
||||
{error ? (
|
||||
<p className='min-w-0 flex-1 truncate text-[11px] text-[var(--text-error)] leading-tight'>
|
||||
<p className='min-w-0 flex-1 truncate text-[12px] text-[var(--text-error)] leading-tight'>
|
||||
{error}
|
||||
</p>
|
||||
) : (
|
||||
|
||||
@@ -332,7 +332,7 @@ export function CreateBaseModal({ open, onOpenChange }: CreateBaseModalProps) {
|
||||
|
||||
return (
|
||||
<Modal open={open} onOpenChange={handleClose}>
|
||||
<ModalContent>
|
||||
<ModalContent size='lg'>
|
||||
<ModalHeader>Create Knowledge Base</ModalHeader>
|
||||
|
||||
<form onSubmit={handleSubmit(onSubmit)} className='flex min-h-0 flex-1 flex-col'>
|
||||
@@ -528,7 +528,7 @@ export function CreateBaseModal({ open, onOpenChange }: CreateBaseModalProps) {
|
||||
)}
|
||||
|
||||
{fileError && (
|
||||
<p className='text-[11px] text-[var(--text-error)] leading-tight'>{fileError}</p>
|
||||
<p className='text-[12px] text-[var(--text-error)] leading-tight'>{fileError}</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
@@ -537,7 +537,7 @@ export function CreateBaseModal({ open, onOpenChange }: CreateBaseModalProps) {
|
||||
<ModalFooter>
|
||||
<div className='flex w-full items-center justify-between gap-[12px]'>
|
||||
{submitStatus?.type === 'error' || uploadError ? (
|
||||
<p className='min-w-0 flex-1 truncate text-[11px] text-[var(--text-error)] leading-tight'>
|
||||
<p className='min-w-0 flex-1 truncate text-[12px] text-[var(--text-error)] leading-tight'>
|
||||
{uploadError?.message || submitStatus?.message}
|
||||
</p>
|
||||
) : (
|
||||
|
||||
@@ -38,7 +38,7 @@ export function DeleteKnowledgeBaseModal({
|
||||
}: DeleteKnowledgeBaseModalProps) {
|
||||
return (
|
||||
<Modal open={isOpen} onOpenChange={onClose}>
|
||||
<ModalContent className='w-[400px]'>
|
||||
<ModalContent size='sm'>
|
||||
<ModalHeader>Delete Knowledge Base</ModalHeader>
|
||||
<ModalBody>
|
||||
<p className='text-[12px] text-[var(--text-secondary)]'>
|
||||
@@ -55,7 +55,7 @@ export function DeleteKnowledgeBaseModal({
|
||||
</p>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<Button variant='active' onClick={onClose} disabled={isDeleting}>
|
||||
<Button variant='default' onClick={onClose} disabled={isDeleting}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button variant='destructive' onClick={onConfirm} disabled={isDeleting}>
|
||||
|
||||
@@ -98,7 +98,7 @@ export function EditKnowledgeBaseModal({
|
||||
|
||||
return (
|
||||
<Modal open={open} onOpenChange={onOpenChange}>
|
||||
<ModalContent>
|
||||
<ModalContent size='sm'>
|
||||
<ModalHeader>Edit Knowledge Base</ModalHeader>
|
||||
|
||||
<form onSubmit={handleSubmit(onSubmit)} className='flex min-h-0 flex-1 flex-col'>
|
||||
@@ -118,7 +118,7 @@ export function EditKnowledgeBaseModal({
|
||||
data-form-type='other'
|
||||
/>
|
||||
{errors.name && (
|
||||
<p className='text-[11px] text-[var(--text-error)]'>{errors.name.message}</p>
|
||||
<p className='text-[12px] text-[var(--text-error)]'>{errors.name.message}</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -132,7 +132,7 @@ export function EditKnowledgeBaseModal({
|
||||
className={cn(errors.description && 'border-[var(--text-error)]')}
|
||||
/>
|
||||
{errors.description && (
|
||||
<p className='text-[11px] text-[var(--text-error)]'>
|
||||
<p className='text-[12px] text-[var(--text-error)]'>
|
||||
{errors.description.message}
|
||||
</p>
|
||||
)}
|
||||
@@ -143,7 +143,7 @@ export function EditKnowledgeBaseModal({
|
||||
<ModalFooter>
|
||||
<div className='flex w-full items-center justify-between gap-[12px]'>
|
||||
{error ? (
|
||||
<p className='min-w-0 flex-1 truncate text-[11px] text-[var(--text-error)] leading-tight'>
|
||||
<p className='min-w-0 flex-1 truncate text-[12px] text-[var(--text-error)] leading-tight'>
|
||||
{error}
|
||||
</p>
|
||||
) : (
|
||||
|
||||
@@ -112,7 +112,7 @@ export function SlackChannelSelector({
|
||||
{selectedChannel.isPrivate ? 'Private' : 'Public'} channel: #{selectedChannel.name}
|
||||
</p>
|
||||
)}
|
||||
{error && <p className='text-[11px] text-[var(--text-error)]'>{error}</p>}
|
||||
{error && <p className='text-[12px] text-[var(--text-error)]'>{error}</p>}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -634,7 +634,7 @@ export function NotificationSettings({
|
||||
}}
|
||||
/>
|
||||
{formErrors.webhookUrl && (
|
||||
<p className='text-[11px] text-[var(--text-error)]'>{formErrors.webhookUrl}</p>
|
||||
<p className='text-[12px] text-[var(--text-error)]'>{formErrors.webhookUrl}</p>
|
||||
)}
|
||||
</div>
|
||||
<div className='flex flex-col gap-[8px]'>
|
||||
@@ -660,7 +660,7 @@ export function NotificationSettings({
|
||||
placeholderWithTags='Add email'
|
||||
/>
|
||||
{formErrors.emailRecipients && (
|
||||
<p className='text-[11px] text-[var(--text-error)]'>{formErrors.emailRecipients}</p>
|
||||
<p className='text-[12px] text-[var(--text-error)]'>{formErrors.emailRecipients}</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
@@ -707,7 +707,7 @@ export function NotificationSettings({
|
||||
/>
|
||||
)}
|
||||
{formErrors.slackAccountId && (
|
||||
<p className='text-[11px] text-[var(--text-error)]'>
|
||||
<p className='text-[12px] text-[var(--text-error)]'>
|
||||
{formErrors.slackAccountId}
|
||||
</p>
|
||||
)}
|
||||
@@ -776,7 +776,7 @@ export function NotificationSettings({
|
||||
allOptionLabel='All levels'
|
||||
/>
|
||||
{formErrors.levelFilter && (
|
||||
<p className='text-[11px] text-[var(--text-error)]'>{formErrors.levelFilter}</p>
|
||||
<p className='text-[12px] text-[var(--text-error)]'>{formErrors.levelFilter}</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -822,7 +822,7 @@ export function NotificationSettings({
|
||||
allOptionLabel='All triggers'
|
||||
/>
|
||||
{formErrors.triggerFilter && (
|
||||
<p className='text-[11px] text-[var(--text-error)]'>{formErrors.triggerFilter}</p>
|
||||
<p className='text-[12px] text-[var(--text-error)]'>{formErrors.triggerFilter}</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -938,7 +938,7 @@ export function NotificationSettings({
|
||||
}
|
||||
/>
|
||||
{formErrors.consecutiveFailures && (
|
||||
<p className='text-[11px] text-[var(--text-error)]'>
|
||||
<p className='text-[12px] text-[var(--text-error)]'>
|
||||
{formErrors.consecutiveFailures}
|
||||
</p>
|
||||
)}
|
||||
@@ -962,7 +962,7 @@ export function NotificationSettings({
|
||||
}
|
||||
/>
|
||||
{formErrors.failureRatePercent && (
|
||||
<p className='text-[11px] text-[var(--text-error)]'>
|
||||
<p className='text-[12px] text-[var(--text-error)]'>
|
||||
{formErrors.failureRatePercent}
|
||||
</p>
|
||||
)}
|
||||
@@ -982,7 +982,7 @@ export function NotificationSettings({
|
||||
}
|
||||
/>
|
||||
{formErrors.windowHours && (
|
||||
<p className='text-[11px] text-[var(--text-error)]'>{formErrors.windowHours}</p>
|
||||
<p className='text-[12px] text-[var(--text-error)]'>{formErrors.windowHours}</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
@@ -1004,7 +1004,7 @@ export function NotificationSettings({
|
||||
}
|
||||
/>
|
||||
{formErrors.durationThresholdMs && (
|
||||
<p className='text-[11px] text-[var(--text-error)]'>
|
||||
<p className='text-[12px] text-[var(--text-error)]'>
|
||||
{formErrors.durationThresholdMs}
|
||||
</p>
|
||||
)}
|
||||
@@ -1028,7 +1028,7 @@ export function NotificationSettings({
|
||||
}
|
||||
/>
|
||||
{formErrors.latencySpikePercent && (
|
||||
<p className='text-[11px] text-[var(--text-error)]'>
|
||||
<p className='text-[12px] text-[var(--text-error)]'>
|
||||
{formErrors.latencySpikePercent}
|
||||
</p>
|
||||
)}
|
||||
@@ -1048,7 +1048,7 @@ export function NotificationSettings({
|
||||
}
|
||||
/>
|
||||
{formErrors.windowHours && (
|
||||
<p className='text-[11px] text-[var(--text-error)]'>{formErrors.windowHours}</p>
|
||||
<p className='text-[12px] text-[var(--text-error)]'>{formErrors.windowHours}</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
@@ -1071,7 +1071,7 @@ export function NotificationSettings({
|
||||
}
|
||||
/>
|
||||
{formErrors.costThresholdDollars && (
|
||||
<p className='text-[11px] text-[var(--text-error)]'>
|
||||
<p className='text-[12px] text-[var(--text-error)]'>
|
||||
{formErrors.costThresholdDollars}
|
||||
</p>
|
||||
)}
|
||||
@@ -1094,7 +1094,7 @@ export function NotificationSettings({
|
||||
}
|
||||
/>
|
||||
{formErrors.inactivityHours && (
|
||||
<p className='text-[11px] text-[var(--text-error)]'>{formErrors.inactivityHours}</p>
|
||||
<p className='text-[12px] text-[var(--text-error)]'>{formErrors.inactivityHours}</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
@@ -1116,7 +1116,7 @@ export function NotificationSettings({
|
||||
}
|
||||
/>
|
||||
{formErrors.errorCountThreshold && (
|
||||
<p className='text-[11px] text-[var(--text-error)]'>
|
||||
<p className='text-[12px] text-[var(--text-error)]'>
|
||||
{formErrors.errorCountThreshold}
|
||||
</p>
|
||||
)}
|
||||
@@ -1136,7 +1136,7 @@ export function NotificationSettings({
|
||||
}
|
||||
/>
|
||||
{formErrors.windowHours && (
|
||||
<p className='text-[11px] text-[var(--text-error)]'>{formErrors.windowHours}</p>
|
||||
<p className='text-[12px] text-[var(--text-error)]'>{formErrors.windowHours}</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
@@ -1261,7 +1261,7 @@ export function NotificationSettings({
|
||||
</Modal>
|
||||
|
||||
<Modal open={showDeleteDialog} onOpenChange={setShowDeleteDialog}>
|
||||
<ModalContent className='w-[400px]'>
|
||||
<ModalContent size='sm'>
|
||||
<ModalHeader>Delete Notification</ModalHeader>
|
||||
<ModalBody>
|
||||
<p className='text-[12px] text-[var(--text-secondary)]'>
|
||||
|
||||
@@ -2,6 +2,7 @@ import { memo, useCallback } from 'react'
|
||||
import { ArrowLeftRight, ArrowUpDown, Circle, CircleOff, LogOut } from 'lucide-react'
|
||||
import { Button, Copy, Tooltip, Trash2 } from '@/components/emcn'
|
||||
import { cn } from '@/lib/core/utils/cn'
|
||||
import { isValidStartBlockType } from '@/lib/workflows/triggers/start-block-types'
|
||||
import { useUserPermissionsContext } from '@/app/workspace/[workspaceId]/providers/workspace-permissions-provider'
|
||||
import { useCollaborativeWorkflow } from '@/hooks/use-collaborative-workflow'
|
||||
import { useWorkflowRegistry } from '@/stores/workflows/registry/store'
|
||||
@@ -97,7 +98,7 @@ export const ActionBar = memo(
|
||||
|
||||
const userPermissions = useUserPermissionsContext()
|
||||
|
||||
const isStartBlock = blockType === 'starter' || blockType === 'start_trigger'
|
||||
const isStartBlock = isValidStartBlockType(blockType)
|
||||
const isResponseBlock = blockType === 'response'
|
||||
const isNoteBlock = blockType === 'note'
|
||||
const isSubflowBlock = blockType === 'loop' || blockType === 'parallel'
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
PopoverDivider,
|
||||
PopoverItem,
|
||||
} from '@/components/emcn'
|
||||
import { isValidStartBlockType } from '@/lib/workflows/triggers/start-block-types'
|
||||
|
||||
/**
|
||||
* Block information for context menu actions
|
||||
@@ -73,9 +74,7 @@ export function BlockMenu({
|
||||
const allEnabled = selectedBlocks.every((b) => b.enabled)
|
||||
const allDisabled = selectedBlocks.every((b) => !b.enabled)
|
||||
|
||||
const hasStarterBlock = selectedBlocks.some(
|
||||
(b) => b.type === 'starter' || b.type === 'start_trigger'
|
||||
)
|
||||
const hasStarterBlock = selectedBlocks.some((b) => isValidStartBlockType(b.type))
|
||||
const allNoteBlocks = selectedBlocks.every((b) => b.type === 'note')
|
||||
const isSubflow =
|
||||
isSingleBlock && (selectedBlocks[0]?.type === 'loop' || selectedBlocks[0]?.type === 'parallel')
|
||||
|
||||
@@ -995,7 +995,7 @@ export function Chat() {
|
||||
<div className='flex items-start gap-2'>
|
||||
<AlertCircle className='mt-0.5 h-3 w-3 shrink-0 text-[var(--text-error)]' />
|
||||
<div className='flex-1'>
|
||||
<div className='mb-1 font-medium text-[11px] text-[var(--text-error)]'>
|
||||
<div className='mb-1 font-medium text-[12px] text-[var(--text-error)]'>
|
||||
File upload error
|
||||
</div>
|
||||
<div className='space-y-1'>
|
||||
|
||||
@@ -557,7 +557,7 @@ function IdentifierInput({
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{error && <p className='mt-[6.5px] text-[11px] text-[var(--text-error)]'>{error}</p>}
|
||||
{error && <p className='mt-[6.5px] text-[12px] text-[var(--text-error)]'>{error}</p>}
|
||||
<p className='mt-[6.5px] truncate text-[11px] text-[var(--text-secondary)]'>
|
||||
{isEditingExisting && value ? (
|
||||
<>
|
||||
@@ -777,7 +777,7 @@ function AuthSelector({
|
||||
disabled={disabled}
|
||||
/>
|
||||
{emailError && (
|
||||
<p className='mt-[6.5px] text-[11px] text-[var(--text-error)]'>{emailError}</p>
|
||||
<p className='mt-[6.5px] text-[12px] text-[var(--text-error)]'>{emailError}</p>
|
||||
)}
|
||||
<p className='mt-[6.5px] text-[11px] text-[var(--text-secondary)]'>
|
||||
{authType === 'email'
|
||||
@@ -787,7 +787,7 @@ function AuthSelector({
|
||||
</div>
|
||||
)}
|
||||
|
||||
{error && <p className='mt-[6.5px] text-[11px] text-[var(--text-error)]'>{error}</p>}
|
||||
{error && <p className='mt-[6.5px] text-[12px] text-[var(--text-error)]'>{error}</p>}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -216,7 +216,7 @@ export function FormBuilder({
|
||||
)}
|
||||
</div>
|
||||
{titleError && (
|
||||
<p className='mt-[4px] text-[11px] text-[var(--text-error)]'>{titleError}</p>
|
||||
<p className='mt-[4px] text-[12px] text-[var(--text-error)]'>{titleError}</p>
|
||||
)}
|
||||
<div className='mt-[4px] flex items-center gap-[6px]'>
|
||||
<input
|
||||
|
||||
@@ -17,6 +17,7 @@ import { Skeleton } from '@/components/ui'
|
||||
import { isDev } from '@/lib/core/config/feature-flags'
|
||||
import { cn } from '@/lib/core/utils/cn'
|
||||
import { getBaseUrl, getEmailDomain } from '@/lib/core/utils/urls'
|
||||
import { isValidStartBlockType } from '@/lib/workflows/triggers/start-block-types'
|
||||
import {
|
||||
type FieldConfig,
|
||||
useCreateForm,
|
||||
@@ -146,7 +147,7 @@ export function FormDeploy({
|
||||
|
||||
useEffect(() => {
|
||||
const blocks = Object.values(useWorkflowStore.getState().blocks)
|
||||
const startBlock = blocks.find((b) => b.type === 'starter' || b.type === 'start_trigger')
|
||||
const startBlock = blocks.find((b) => isValidStartBlockType(b.type))
|
||||
|
||||
if (startBlock) {
|
||||
const inputFormat = useSubBlockStore.getState().getValue(startBlock.id, 'inputFormat')
|
||||
@@ -398,7 +399,7 @@ export function FormDeploy({
|
||||
</div>
|
||||
</div>
|
||||
{(identifierError || errors.identifier) && (
|
||||
<p className='mt-[6.5px] text-[11px] text-[var(--text-error)]'>
|
||||
<p className='mt-[6.5px] text-[12px] text-[var(--text-error)]'>
|
||||
{identifierError || errors.identifier}
|
||||
</p>
|
||||
)}
|
||||
@@ -482,7 +483,7 @@ export function FormDeploy({
|
||||
</button>
|
||||
</div>
|
||||
{errors.password && (
|
||||
<p className='mt-[6.5px] text-[11px] text-[var(--text-error)]'>{errors.password}</p>
|
||||
<p className='mt-[6.5px] text-[12px] text-[var(--text-error)]'>{errors.password}</p>
|
||||
)}
|
||||
<p className='mt-[6.5px] text-[11px] text-[var(--text-secondary)]'>
|
||||
{existingForm?.hasPassword
|
||||
@@ -519,7 +520,7 @@ export function FormDeploy({
|
||||
placeholderWithTags='Add another'
|
||||
/>
|
||||
{errors.emails && (
|
||||
<p className='mt-[6.5px] text-[11px] text-[var(--text-error)]'>{errors.emails}</p>
|
||||
<p className='mt-[6.5px] text-[12px] text-[var(--text-error)]'>{errors.emails}</p>
|
||||
)}
|
||||
<p className='mt-[6.5px] text-[11px] text-[var(--text-secondary)]'>
|
||||
Add specific emails or entire domains (@example.com)
|
||||
@@ -550,7 +551,7 @@ export function FormDeploy({
|
||||
)}
|
||||
|
||||
{errors.general && (
|
||||
<p className='mt-[6.5px] text-[11px] text-[var(--text-error)]'>{errors.general}</p>
|
||||
<p className='mt-[6.5px] text-[12px] text-[var(--text-error)]'>{errors.general}</p>
|
||||
)}
|
||||
|
||||
<button type='button' data-delete-trigger onClick={handleDelete} className='hidden' />
|
||||
|
||||
@@ -15,7 +15,7 @@ import {
|
||||
import { Skeleton } from '@/components/ui'
|
||||
import { generateToolInputSchema, sanitizeToolName } from '@/lib/mcp/workflow-tool-schema'
|
||||
import { normalizeInputFormatValue } from '@/lib/workflows/input-format'
|
||||
import { isValidStartBlockType } from '@/lib/workflows/triggers/trigger-utils'
|
||||
import { isValidStartBlockType } from '@/lib/workflows/triggers/start-block-types'
|
||||
import type { InputFormatField } from '@/lib/workflows/types'
|
||||
import {
|
||||
useAddWorkflowMcpTool,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use client'
|
||||
|
||||
import { useMemo } from 'react'
|
||||
import { useMemo, useState } from 'react'
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { Check } from 'lucide-react'
|
||||
import {
|
||||
@@ -308,6 +308,7 @@ export function OAuthRequiredModal({
|
||||
serviceId,
|
||||
newScopes = [],
|
||||
}: OAuthRequiredModalProps) {
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
const { baseProvider } = parseProvider(provider)
|
||||
const baseProviderConfig = OAUTH_PROVIDERS[baseProvider]
|
||||
|
||||
@@ -348,23 +349,24 @@ export function OAuthRequiredModal({
|
||||
}, [requiredScopes, newScopesSet])
|
||||
|
||||
const handleConnectDirectly = async () => {
|
||||
setError(null)
|
||||
|
||||
try {
|
||||
const providerId = getProviderIdFromServiceId(serviceId)
|
||||
|
||||
onClose()
|
||||
|
||||
logger.info('Linking OAuth2:', {
|
||||
providerId,
|
||||
requiredScopes,
|
||||
})
|
||||
|
||||
if (providerId === 'trello') {
|
||||
onClose()
|
||||
window.location.href = '/api/auth/trello/authorize'
|
||||
return
|
||||
}
|
||||
|
||||
if (providerId === 'shopify') {
|
||||
// Pass the current URL so we can redirect back after OAuth
|
||||
onClose()
|
||||
const returnUrl = encodeURIComponent(window.location.href)
|
||||
window.location.href = `/api/auth/shopify/authorize?returnUrl=${returnUrl}`
|
||||
return
|
||||
@@ -374,8 +376,10 @@ export function OAuthRequiredModal({
|
||||
providerId,
|
||||
callbackURL: window.location.href,
|
||||
})
|
||||
} catch (error) {
|
||||
logger.error('Error initiating OAuth flow:', { error })
|
||||
onClose()
|
||||
} catch (err) {
|
||||
logger.error('Error initiating OAuth flow:', { error: err })
|
||||
setError('Failed to connect. Please try again.')
|
||||
}
|
||||
}
|
||||
|
||||
@@ -425,10 +429,12 @@ export function OAuthRequiredModal({
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{error && <p className='text-[12px] text-[var(--text-error)]'>{error}</p>}
|
||||
</div>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<Button variant='active' onClick={onClose}>
|
||||
<Button variant='default' onClick={onClose}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button variant='tertiary' type='button' onClick={handleConnectDirectly}>
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { AlertCircle, Wand2 } from 'lucide-react'
|
||||
import { AlertCircle, ArrowUp } from 'lucide-react'
|
||||
import { useParams } from 'next/navigation'
|
||||
import {
|
||||
Badge,
|
||||
Button,
|
||||
Input,
|
||||
Modal,
|
||||
ModalBody,
|
||||
ModalContent,
|
||||
@@ -878,35 +879,53 @@ try {
|
||||
JSON Schema
|
||||
</Label>
|
||||
{schemaError && (
|
||||
<div className='ml-2 flex min-w-0 items-center gap-1 text-[var(--text-error)] text-xs'>
|
||||
<div className='ml-2 flex min-w-0 items-center gap-1 text-[12px] text-[var(--text-error)]'>
|
||||
<AlertCircle className='h-3 w-3 flex-shrink-0' />
|
||||
<span className='truncate'>{schemaError}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className='flex min-w-0 flex-1 items-center justify-end gap-1 pr-[4px]'>
|
||||
<div className='flex min-w-0 items-center justify-end gap-[4px]'>
|
||||
{!isSchemaPromptActive ? (
|
||||
<button
|
||||
type='button'
|
||||
<Button
|
||||
variant='active'
|
||||
className='-my-1 h-5 px-2 py-0 text-[11px]'
|
||||
onClick={handleSchemaWandClick}
|
||||
disabled={schemaGeneration.isLoading || schemaGeneration.isStreaming}
|
||||
className='inline-flex h-[16px] w-[16px] items-center justify-center rounded-full hover:bg-transparent disabled:opacity-50'
|
||||
aria-label='Generate schema with AI'
|
||||
>
|
||||
<Wand2 className='!h-[12px] !w-[12px] text-[var(--text-secondary)]' />
|
||||
</button>
|
||||
Generate
|
||||
</Button>
|
||||
) : (
|
||||
<input
|
||||
ref={schemaPromptInputRef}
|
||||
type='text'
|
||||
value={schemaGeneration.isStreaming ? 'Generating...' : schemaPromptInput}
|
||||
onChange={(e) => handleSchemaPromptChange(e.target.value)}
|
||||
onBlur={handleSchemaPromptBlur}
|
||||
onKeyDown={handleSchemaPromptKeyDown}
|
||||
disabled={schemaGeneration.isStreaming}
|
||||
className='h-[16px] w-full border-none bg-transparent py-0 pr-[2px] text-right font-medium text-[12px] text-[var(--text-primary)] leading-[14px] placeholder:text-[var(--text-muted)] focus:outline-none'
|
||||
placeholder='Describe schema...'
|
||||
/>
|
||||
<div className='-my-1 flex items-center gap-[4px]'>
|
||||
<Input
|
||||
ref={schemaPromptInputRef}
|
||||
value={schemaGeneration.isStreaming ? 'Generating...' : schemaPromptInput}
|
||||
onChange={(e) => handleSchemaPromptChange(e.target.value)}
|
||||
onBlur={handleSchemaPromptBlur}
|
||||
onKeyDown={handleSchemaPromptKeyDown}
|
||||
disabled={schemaGeneration.isStreaming}
|
||||
className={cn(
|
||||
'h-5 max-w-[200px] flex-1 text-[11px]',
|
||||
schemaGeneration.isStreaming && 'text-muted-foreground'
|
||||
)}
|
||||
placeholder='Generate...'
|
||||
/>
|
||||
<Button
|
||||
variant='tertiary'
|
||||
disabled={!schemaPromptInput.trim() || schemaGeneration.isStreaming}
|
||||
onMouseDown={(e) => {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
}}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
handleSchemaPromptSubmit()
|
||||
}}
|
||||
className='h-[20px] w-[20px] flex-shrink-0 p-0'
|
||||
>
|
||||
<ArrowUp className='h-[12px] w-[12px]' />
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
@@ -952,35 +971,53 @@ try {
|
||||
Code
|
||||
</Label>
|
||||
{codeError && !codeGeneration.isStreaming && (
|
||||
<div className='ml-2 flex min-w-0 items-center gap-1 text-[var(--text-error)] text-xs'>
|
||||
<div className='ml-2 flex min-w-0 items-center gap-1 text-[12px] text-[var(--text-error)]'>
|
||||
<AlertCircle className='h-3 w-3 flex-shrink-0' />
|
||||
<span className='truncate'>{codeError}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className='flex min-w-0 flex-1 items-center justify-end gap-1 pr-[4px]'>
|
||||
<div className='flex min-w-0 items-center justify-end gap-[4px]'>
|
||||
{!isCodePromptActive ? (
|
||||
<button
|
||||
type='button'
|
||||
<Button
|
||||
variant='active'
|
||||
className='-my-1 h-5 px-2 py-0 text-[11px]'
|
||||
onClick={handleCodeWandClick}
|
||||
disabled={codeGeneration.isLoading || codeGeneration.isStreaming}
|
||||
className='inline-flex h-[16px] w-[16px] items-center justify-center rounded-full hover:bg-transparent disabled:opacity-50'
|
||||
aria-label='Generate code with AI'
|
||||
>
|
||||
<Wand2 className='!h-[12px] !w-[12px] text-[var(--text-secondary)]' />
|
||||
</button>
|
||||
Generate
|
||||
</Button>
|
||||
) : (
|
||||
<input
|
||||
ref={codePromptInputRef}
|
||||
type='text'
|
||||
value={codeGeneration.isStreaming ? 'Generating...' : codePromptInput}
|
||||
onChange={(e) => handleCodePromptChange(e.target.value)}
|
||||
onBlur={handleCodePromptBlur}
|
||||
onKeyDown={handleCodePromptKeyDown}
|
||||
disabled={codeGeneration.isStreaming}
|
||||
className='h-[16px] w-full border-none bg-transparent py-0 pr-[2px] text-right font-medium text-[12px] text-[var(--text-primary)] leading-[14px] placeholder:text-[var(--text-muted)] focus:outline-none'
|
||||
placeholder='Describe code...'
|
||||
/>
|
||||
<div className='-my-1 flex items-center gap-[4px]'>
|
||||
<Input
|
||||
ref={codePromptInputRef}
|
||||
value={codeGeneration.isStreaming ? 'Generating...' : codePromptInput}
|
||||
onChange={(e) => handleCodePromptChange(e.target.value)}
|
||||
onBlur={handleCodePromptBlur}
|
||||
onKeyDown={handleCodePromptKeyDown}
|
||||
disabled={codeGeneration.isStreaming}
|
||||
className={cn(
|
||||
'h-5 max-w-[200px] flex-1 text-[11px]',
|
||||
codeGeneration.isStreaming && 'text-muted-foreground'
|
||||
)}
|
||||
placeholder='Generate...'
|
||||
/>
|
||||
<Button
|
||||
variant='tertiary'
|
||||
disabled={!codePromptInput.trim() || codeGeneration.isStreaming}
|
||||
onMouseDown={(e) => {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
}}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
handleCodePromptSubmit()
|
||||
}}
|
||||
className='h-[20px] w-[20px] flex-shrink-0 p-0'
|
||||
>
|
||||
<ArrowUp className='h-[12px] w-[12px]' />
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -556,14 +556,17 @@ export function Panel() {
|
||||
<ModalHeader>Delete Workflow</ModalHeader>
|
||||
<ModalBody>
|
||||
<p className='text-[12px] text-[var(--text-secondary)]'>
|
||||
Deleting this workflow will permanently remove all associated blocks, executions, and
|
||||
configuration.{' '}
|
||||
Are you sure you want to delete{' '}
|
||||
<span className='font-medium text-[var(--text-primary)]'>
|
||||
{currentWorkflow?.name ?? 'this workflow'}
|
||||
</span>
|
||||
? This will permanently remove all associated blocks, executions, and configuration.{' '}
|
||||
<span className='text-[var(--text-error)]'>This action cannot be undone.</span>
|
||||
</p>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<Button
|
||||
variant='active'
|
||||
variant='default'
|
||||
onClick={() => setIsDeleteModalOpen(false)}
|
||||
disabled={isDeleting}
|
||||
>
|
||||
|
||||
@@ -2,6 +2,7 @@ import { useMemo } from 'react'
|
||||
import { useShallow } from 'zustand/react/shallow'
|
||||
import { BlockPathCalculator } from '@/lib/workflows/blocks/block-path-calculator'
|
||||
import { SYSTEM_REFERENCE_PREFIXES } from '@/lib/workflows/sanitization/references'
|
||||
import { isValidStartBlockType } from '@/lib/workflows/triggers/start-block-types'
|
||||
import { normalizeName } from '@/executor/constants'
|
||||
import { useWorkflowStore } from '@/stores/workflows/workflow/store'
|
||||
import type { Loop, Parallel } from '@/stores/workflows/workflow/types'
|
||||
@@ -26,9 +27,7 @@ export function useAccessibleReferencePrefixes(blockId?: string | null): Set<str
|
||||
const accessibleIds = new Set<string>(ancestorIds)
|
||||
accessibleIds.add(blockId)
|
||||
|
||||
const starterBlock = Object.values(blocks).find(
|
||||
(block) => block.type === 'starter' || block.type === 'start_trigger'
|
||||
)
|
||||
const starterBlock = Object.values(blocks).find((block) => isValidStartBlockType(block.type))
|
||||
if (starterBlock && ancestorIds.includes(starterBlock.id)) {
|
||||
accessibleIds.add(starterBlock.id)
|
||||
}
|
||||
|
||||
@@ -420,7 +420,7 @@ export function HelpModal({ open, onOpenChange, workflowId, workspaceId }: HelpM
|
||||
|
||||
return (
|
||||
<Modal open={open} onOpenChange={onOpenChange}>
|
||||
<ModalContent>
|
||||
<ModalContent size='md'>
|
||||
<ModalHeader>Help & Support</ModalHeader>
|
||||
|
||||
<form onSubmit={handleSubmit(onSubmit)} className='flex min-h-0 flex-1 flex-col'>
|
||||
|
||||
@@ -1069,7 +1069,7 @@ export function AccessControl() {
|
||||
</Modal>
|
||||
|
||||
<Modal open={showUnsavedChanges} onOpenChange={setShowUnsavedChanges}>
|
||||
<ModalContent className='w-[400px]'>
|
||||
<ModalContent size='sm'>
|
||||
<ModalHeader>Unsaved Changes</ModalHeader>
|
||||
<ModalBody>
|
||||
<p className='text-[12px] text-[var(--text-tertiary)]'>
|
||||
@@ -1185,7 +1185,7 @@ export function AccessControl() {
|
||||
</div>
|
||||
|
||||
<Modal open={showCreateModal} onOpenChange={handleCloseCreateModal}>
|
||||
<ModalContent className='w-[400px]'>
|
||||
<ModalContent size='sm'>
|
||||
<ModalHeader>Create Permission Group</ModalHeader>
|
||||
<ModalBody>
|
||||
<div className='flex flex-col gap-[12px]'>
|
||||
@@ -1237,7 +1237,7 @@ export function AccessControl() {
|
||||
</Modal>
|
||||
|
||||
<Modal open={!!deletingGroup} onOpenChange={() => setDeletingGroup(null)}>
|
||||
<ModalContent className='w-[400px]'>
|
||||
<ModalContent size='sm'>
|
||||
<ModalHeader>Delete Permission Group</ModalHeader>
|
||||
<ModalBody>
|
||||
<p className='text-[12px] text-[var(--text-secondary)]'>
|
||||
|
||||
@@ -392,7 +392,7 @@ export function ApiKeys({ onOpenChange, registerCloseHandler }: ApiKeysProps) {
|
||||
|
||||
{/* Delete Confirmation Dialog */}
|
||||
<Modal open={showDeleteDialog} onOpenChange={setShowDeleteDialog}>
|
||||
<ModalContent className='w-[400px]'>
|
||||
<ModalContent size='sm'>
|
||||
<ModalHeader>Delete API key</ModalHeader>
|
||||
<ModalBody>
|
||||
<p className='text-[12px] text-[var(--text-secondary)]'>
|
||||
|
||||
@@ -112,7 +112,7 @@ export function CreateApiKeyModal({
|
||||
<>
|
||||
{/* Create API Key Dialog */}
|
||||
<Modal open={open} onOpenChange={onOpenChange}>
|
||||
<ModalContent className='w-[400px]'>
|
||||
<ModalContent size='sm'>
|
||||
<ModalHeader>Create new API key</ModalHeader>
|
||||
<ModalBody>
|
||||
<p className='text-[12px] text-[var(--text-tertiary)]'>
|
||||
@@ -176,7 +176,7 @@ export function CreateApiKeyModal({
|
||||
data-form-type='other'
|
||||
/>
|
||||
{createError && (
|
||||
<p className='text-[11px] text-[var(--text-error)] leading-tight'>
|
||||
<p className='text-[12px] text-[var(--text-error)] leading-tight'>
|
||||
{createError}
|
||||
</p>
|
||||
)}
|
||||
@@ -215,7 +215,7 @@ export function CreateApiKeyModal({
|
||||
}
|
||||
}}
|
||||
>
|
||||
<ModalContent className='w-[400px]'>
|
||||
<ModalContent size='sm'>
|
||||
<ModalHeader>Your API key has been created</ModalHeader>
|
||||
<ModalBody>
|
||||
<p className='text-[12px] text-[var(--text-tertiary)]'>
|
||||
|
||||
@@ -276,7 +276,7 @@ export function BYOK() {
|
||||
</Button>
|
||||
</div>
|
||||
{error && (
|
||||
<p className='text-[11px] text-[var(--text-error)] leading-tight'>{error}</p>
|
||||
<p className='text-[12px] text-[var(--text-error)] leading-tight'>{error}</p>
|
||||
)}
|
||||
</div>
|
||||
</ModalBody>
|
||||
@@ -306,7 +306,7 @@ export function BYOK() {
|
||||
</Modal>
|
||||
|
||||
<Modal open={!!deleteConfirmProvider} onOpenChange={() => setDeleteConfirmProvider(null)}>
|
||||
<ModalContent className='w-[400px]'>
|
||||
<ModalContent size='sm'>
|
||||
<ModalHeader>Delete API Key</ModalHeader>
|
||||
<ModalBody>
|
||||
<p className='text-[12px] text-[var(--text-tertiary)]'>
|
||||
|
||||
@@ -211,7 +211,7 @@ export function Copilot() {
|
||||
|
||||
{/* Create API Key Dialog */}
|
||||
<Modal open={isCreateDialogOpen} onOpenChange={setIsCreateDialogOpen}>
|
||||
<ModalContent className='w-[400px]'>
|
||||
<ModalContent size='sm'>
|
||||
<ModalHeader>Create new API key</ModalHeader>
|
||||
<ModalBody>
|
||||
<p className='text-[12px] text-[var(--text-tertiary)]'>
|
||||
@@ -234,7 +234,7 @@ export function Copilot() {
|
||||
autoFocus
|
||||
/>
|
||||
{createError && (
|
||||
<p className='text-[11px] text-[var(--text-error)] leading-tight'>{createError}</p>
|
||||
<p className='text-[12px] text-[var(--text-error)] leading-tight'>{createError}</p>
|
||||
)}
|
||||
</div>
|
||||
</ModalBody>
|
||||
@@ -273,7 +273,7 @@ export function Copilot() {
|
||||
}
|
||||
}}
|
||||
>
|
||||
<ModalContent className='w-[400px]'>
|
||||
<ModalContent size='sm'>
|
||||
<ModalHeader>Your API key has been created</ModalHeader>
|
||||
<ModalBody>
|
||||
<p className='text-[12px] text-[var(--text-tertiary)]'>
|
||||
@@ -310,7 +310,7 @@ export function Copilot() {
|
||||
|
||||
{/* Delete Confirmation Dialog */}
|
||||
<Modal open={showDeleteDialog} onOpenChange={setShowDeleteDialog}>
|
||||
<ModalContent className='w-[400px]'>
|
||||
<ModalContent size='sm'>
|
||||
<ModalHeader>Delete API key</ModalHeader>
|
||||
<ModalBody>
|
||||
<p className='text-[12px] text-[var(--text-secondary)]'>
|
||||
|
||||
@@ -824,7 +824,7 @@ export function CredentialSets() {
|
||||
|
||||
{/* Create Polling Group Modal */}
|
||||
<Modal open={showCreateModal} onOpenChange={handleCloseCreateModal}>
|
||||
<ModalContent className='w-[400px]'>
|
||||
<ModalContent size='sm'>
|
||||
<ModalHeader>Create Polling Group</ModalHeader>
|
||||
<ModalBody>
|
||||
<div className='flex flex-col gap-[12px]'>
|
||||
@@ -897,7 +897,7 @@ export function CredentialSets() {
|
||||
|
||||
{/* Leave Confirmation Modal */}
|
||||
<Modal open={!!leavingMembership} onOpenChange={() => setLeavingMembership(null)}>
|
||||
<ModalContent className='w-[400px]'>
|
||||
<ModalContent size='sm'>
|
||||
<ModalHeader>Leave Polling Group</ModalHeader>
|
||||
<ModalBody>
|
||||
<p className='text-[12px] text-[var(--text-secondary)]'>
|
||||
@@ -925,7 +925,7 @@ export function CredentialSets() {
|
||||
|
||||
{/* Delete Confirmation Modal */}
|
||||
<Modal open={!!deletingSet} onOpenChange={() => setDeletingSet(null)}>
|
||||
<ModalContent className='w-[400px]'>
|
||||
<ModalContent size='sm'>
|
||||
<ModalHeader>Delete Polling Group</ModalHeader>
|
||||
<ModalBody>
|
||||
<p className='text-[12px] text-[var(--text-secondary)]'>
|
||||
|
||||
@@ -206,7 +206,7 @@ export function CustomTools() {
|
||||
/>
|
||||
|
||||
<Modal open={showDeleteDialog} onOpenChange={setShowDeleteDialog}>
|
||||
<ModalContent className='w-[400px]'>
|
||||
<ModalContent size='sm'>
|
||||
<ModalHeader>Delete Custom Tool</ModalHeader>
|
||||
<ModalBody>
|
||||
<p className='text-[12px] text-[var(--text-secondary)]'>
|
||||
|
||||
@@ -821,7 +821,7 @@ export function EnvironmentVariables({ registerBeforeLeaveHandler }: Environment
|
||||
</div>
|
||||
|
||||
<Modal open={showUnsavedChanges} onOpenChange={setShowUnsavedChanges}>
|
||||
<ModalContent className='w-[400px]'>
|
||||
<ModalContent size='sm'>
|
||||
<ModalHeader>Unsaved Changes</ModalHeader>
|
||||
<ModalBody>
|
||||
<p className='text-[12px] text-[var(--text-tertiary)]'>
|
||||
|
||||
@@ -390,7 +390,7 @@ export function Integrations({ onOpenChange, registerCloseHandler }: Integration
|
||||
</div>
|
||||
|
||||
<Modal open={showDisconnectDialog} onOpenChange={setShowDisconnectDialog}>
|
||||
<ModalContent className='w-[400px]'>
|
||||
<ModalContent size='sm'>
|
||||
<ModalHeader>Disconnect Service</ModalHeader>
|
||||
<ModalBody>
|
||||
<p className='text-[12px] text-[var(--text-secondary)]'>
|
||||
|
||||
@@ -1170,7 +1170,7 @@ export function MCP({ initialServerId }: MCPProps) {
|
||||
</div>
|
||||
|
||||
<Modal open={showDeleteDialog} onOpenChange={setShowDeleteDialog}>
|
||||
<ModalContent className='w-[400px]'>
|
||||
<ModalContent size='sm'>
|
||||
<ModalHeader>Delete MCP Server</ModalHeader>
|
||||
<ModalBody>
|
||||
<p className='text-[12px] text-[var(--text-secondary)]'>
|
||||
|
||||
@@ -245,10 +245,7 @@ export function CancelSubscription({ subscription, subscriptionData }: CancelSub
|
||||
? 'Your subscription is set to cancel at the end of the billing period. Would you like to keep your subscription active?'
|
||||
: `You'll be redirected to Stripe to manage your subscription. You'll keep access until ${formatDate(
|
||||
periodEndDate
|
||||
)}, then downgrade to free plan.`}{' '}
|
||||
{!isCancelAtPeriodEnd && (
|
||||
<span className='text-[var(--text-error)]'>This action cannot be undone.</span>
|
||||
)}
|
||||
)}, then downgrade to free plan. You can restore your subscription at any time.`}
|
||||
</p>
|
||||
|
||||
{!isCancelAtPeriodEnd && (
|
||||
@@ -266,7 +263,7 @@ export function CancelSubscription({ subscription, subscriptionData }: CancelSub
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<Button
|
||||
variant='active'
|
||||
variant='default'
|
||||
onClick={isCancelAtPeriodEnd ? () => setIsDialogOpen(false) : handleKeep}
|
||||
disabled={isLoading}
|
||||
>
|
||||
|
||||
@@ -183,7 +183,7 @@ export function MemberInvitationCard({
|
||||
aria-autocomplete='none'
|
||||
/>
|
||||
{emailError && (
|
||||
<p className='mt-1 text-[11px] text-[var(--text-error)] leading-tight'>
|
||||
<p className='mt-1 text-[12px] text-[var(--text-error)] leading-tight'>
|
||||
{emailError}
|
||||
</p>
|
||||
)}
|
||||
@@ -295,7 +295,7 @@ export function MemberInvitationCard({
|
||||
|
||||
{/* Invitation error - inline */}
|
||||
{invitationError && (
|
||||
<p className='text-[11px] text-[var(--text-error)] leading-tight'>
|
||||
<p className='text-[12px] text-[var(--text-error)] leading-tight'>
|
||||
{invitationError instanceof Error && invitationError.message
|
||||
? invitationError.message
|
||||
: String(invitationError)}
|
||||
|
||||
@@ -104,7 +104,7 @@ export function NoOrganizationView({
|
||||
|
||||
<div className='flex flex-col gap-[8px]'>
|
||||
{error && (
|
||||
<p className='text-[11px] text-[var(--text-error)] leading-tight'>{error}</p>
|
||||
<p className='text-[12px] text-[var(--text-error)] leading-tight'>{error}</p>
|
||||
)}
|
||||
<div className='flex justify-end'>
|
||||
<Button
|
||||
@@ -179,7 +179,7 @@ export function NoOrganizationView({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{error && <p className='text-[11px] text-[var(--text-error)] leading-tight'>{error}</p>}
|
||||
{error && <p className='text-[12px] text-[var(--text-error)] leading-tight'>{error}</p>}
|
||||
|
||||
<ModalFooter>
|
||||
<Button
|
||||
|
||||
@@ -33,13 +33,19 @@ export function RemoveMemberDialog({
|
||||
}: RemoveMemberDialogProps) {
|
||||
return (
|
||||
<Modal open={open} onOpenChange={onOpenChange}>
|
||||
<ModalContent className='w-[400px]'>
|
||||
<ModalContent size='sm'>
|
||||
<ModalHeader>{isSelfRemoval ? 'Leave Organization' : 'Remove Team Member'}</ModalHeader>
|
||||
<ModalBody>
|
||||
<p className='text-[12px] text-[var(--text-secondary)]'>
|
||||
{isSelfRemoval
|
||||
? 'Are you sure you want to leave this organization? You will lose access to all team resources.'
|
||||
: `Are you sure you want to remove ${memberName} from the team?`}{' '}
|
||||
{isSelfRemoval ? (
|
||||
'Are you sure you want to leave this organization? You will lose access to all team resources.'
|
||||
) : (
|
||||
<>
|
||||
Are you sure you want to remove{' '}
|
||||
<span className='font-medium text-[var(--text-primary)]'>{memberName}</span> from
|
||||
the team?
|
||||
</>
|
||||
)}{' '}
|
||||
<span className='text-[var(--text-error)]'>This action cannot be undone.</span>
|
||||
</p>
|
||||
|
||||
@@ -64,14 +70,14 @@ export function RemoveMemberDialog({
|
||||
|
||||
{error && (
|
||||
<div className='mt-[8px]'>
|
||||
<p className='text-[11px] text-[var(--text-error)] leading-tight'>
|
||||
<p className='text-[12px] text-[var(--text-error)] leading-tight'>
|
||||
{error instanceof Error && error.message ? error.message : String(error)}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<Button variant='active' onClick={onCancel}>
|
||||
<Button variant='default' onClick={onCancel}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button variant='destructive' onClick={() => onConfirmRemove(shouldReduceSeats)}>
|
||||
|
||||
@@ -532,7 +532,7 @@ function ServerDetailView({ workspaceId, serverId, onBack }: ServerDetailViewPro
|
||||
</div>
|
||||
|
||||
<Modal open={!!toolToDelete} onOpenChange={(open) => !open && setToolToDelete(null)}>
|
||||
<ModalContent className='w-[400px]'>
|
||||
<ModalContent size='sm'>
|
||||
<ModalHeader>Remove Workflow</ModalHeader>
|
||||
<ModalBody>
|
||||
<p className='text-[12px] text-[var(--text-secondary)]'>
|
||||
@@ -745,7 +745,7 @@ function ServerDetailView({ workspaceId, serverId, onBack }: ServerDetailViewPro
|
||||
}
|
||||
/>
|
||||
{addToolMutation.isError && (
|
||||
<p className='text-[11px] text-[var(--text-error)] leading-tight'>
|
||||
<p className='text-[12px] text-[var(--text-error)] leading-tight'>
|
||||
{addToolMutation.error?.message || 'Failed to add workflow'}
|
||||
</p>
|
||||
)}
|
||||
@@ -1109,7 +1109,7 @@ export function WorkflowMcpServers() {
|
||||
</div>
|
||||
|
||||
<Modal open={!!serverToDelete} onOpenChange={(open) => !open && setServerToDelete(null)}>
|
||||
<ModalContent className='w-[400px]'>
|
||||
<ModalContent size='sm'>
|
||||
<ModalHeader>Delete MCP Server</ModalHeader>
|
||||
<ModalBody>
|
||||
<p className='text-[12px] text-[var(--text-secondary)]'>
|
||||
|
||||
@@ -102,7 +102,7 @@ export function DeleteModal({
|
||||
|
||||
return (
|
||||
<Modal open={isOpen} onOpenChange={onClose}>
|
||||
<ModalContent className='w-[400px]'>
|
||||
<ModalContent size='sm'>
|
||||
<ModalHeader>{title}</ModalHeader>
|
||||
<ModalBody>
|
||||
<p className='text-[12px] text-[var(--text-secondary)]'>
|
||||
@@ -111,7 +111,7 @@ export function DeleteModal({
|
||||
</p>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<Button variant='active' onClick={onClose} disabled={isDeleting}>
|
||||
<Button variant='default' onClick={onClose} disabled={isDeleting}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button variant='destructive' onClick={onConfirm} disabled={isDeleting}>
|
||||
|
||||
@@ -607,7 +607,7 @@ export function InviteModal({ open, onOpenChange, workspaceName }: InviteModalPr
|
||||
onOpenChange(newOpen)
|
||||
}}
|
||||
>
|
||||
<ModalContent className='w-[500px]'>
|
||||
<ModalContent size='md'>
|
||||
<ModalHeader>Invite members to {workspaceName || 'Workspace'}</ModalHeader>
|
||||
|
||||
<form
|
||||
@@ -740,7 +740,7 @@ export function InviteModal({ open, onOpenChange, workspaceName }: InviteModalPr
|
||||
|
||||
{/* Remove Member Confirmation Dialog */}
|
||||
<Modal open={!!memberToRemove} onOpenChange={handleRemoveMemberCancel}>
|
||||
<ModalContent>
|
||||
<ModalContent size='sm'>
|
||||
<ModalHeader>Remove Member</ModalHeader>
|
||||
<ModalBody>
|
||||
<p className='text-[12px] text-[var(--text-secondary)]'>
|
||||
@@ -773,7 +773,7 @@ export function InviteModal({ open, onOpenChange, workspaceName }: InviteModalPr
|
||||
|
||||
{/* Remove Invitation Confirmation Dialog */}
|
||||
<Modal open={!!invitationToRemove} onOpenChange={handleRemoveInvitationCancel}>
|
||||
<ModalContent className='w-[400px]'>
|
||||
<ModalContent size='sm'>
|
||||
<ModalHeader>Cancel Invitation</ModalHeader>
|
||||
<ModalBody>
|
||||
<p className='text-[12px] text-[var(--text-secondary)]'>
|
||||
|
||||
@@ -7,6 +7,11 @@ import {
|
||||
Badge,
|
||||
Button,
|
||||
ChevronDown,
|
||||
Modal,
|
||||
ModalBody,
|
||||
ModalContent,
|
||||
ModalFooter,
|
||||
ModalHeader,
|
||||
PanelLeft,
|
||||
Popover,
|
||||
PopoverContent,
|
||||
@@ -143,6 +148,9 @@ export function WorkspaceHeader({
|
||||
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false)
|
||||
const [isDeleting, setIsDeleting] = useState(false)
|
||||
const [deleteTarget, setDeleteTarget] = useState<Workspace | null>(null)
|
||||
const [isLeaveModalOpen, setIsLeaveModalOpen] = useState(false)
|
||||
const [isLeaving, setIsLeaving] = useState(false)
|
||||
const [leaveTarget, setLeaveTarget] = useState<Workspace | null>(null)
|
||||
const [editingWorkspaceId, setEditingWorkspaceId] = useState<string | null>(null)
|
||||
const [editingName, setEditingName] = useState('')
|
||||
const [isListRenaming, setIsListRenaming] = useState(false)
|
||||
@@ -278,13 +286,35 @@ export function WorkspaceHeader({
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles leave action from context menu
|
||||
* Handles leave action from context menu - shows confirmation modal
|
||||
*/
|
||||
const handleLeaveAction = async () => {
|
||||
if (!capturedWorkspaceRef.current || !onLeaveWorkspace) return
|
||||
const handleLeaveAction = () => {
|
||||
if (!capturedWorkspaceRef.current) return
|
||||
|
||||
await onLeaveWorkspace(capturedWorkspaceRef.current.id)
|
||||
setIsWorkspaceMenuOpen(false)
|
||||
const workspace = workspaces.find((w) => w.id === capturedWorkspaceRef.current?.id)
|
||||
if (workspace) {
|
||||
setLeaveTarget(workspace)
|
||||
setIsLeaveModalOpen(true)
|
||||
setIsWorkspaceMenuOpen(false)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle leave workspace after confirmation
|
||||
*/
|
||||
const handleLeaveWorkspace = async () => {
|
||||
if (!leaveTarget || !onLeaveWorkspace) return
|
||||
|
||||
setIsLeaving(true)
|
||||
try {
|
||||
await onLeaveWorkspace(leaveTarget.id)
|
||||
setIsLeaveModalOpen(false)
|
||||
setLeaveTarget(null)
|
||||
} catch (error) {
|
||||
logger.error('Error leaving workspace:', error)
|
||||
} finally {
|
||||
setIsLeaving(false)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -573,6 +603,32 @@ export function WorkspaceHeader({
|
||||
itemType='workspace'
|
||||
itemName={deleteTarget?.name || activeWorkspaceFull?.name || activeWorkspace?.name}
|
||||
/>
|
||||
{/* Leave Confirmation Modal */}
|
||||
<Modal open={isLeaveModalOpen} onOpenChange={() => setIsLeaveModalOpen(false)}>
|
||||
<ModalContent size='sm'>
|
||||
<ModalHeader>Leave Workspace</ModalHeader>
|
||||
<ModalBody>
|
||||
<p className='text-[12px] text-[var(--text-secondary)]'>
|
||||
Are you sure you want to leave{' '}
|
||||
<span className='font-medium text-[var(--text-primary)]'>{leaveTarget?.name}</span>?
|
||||
You will lose access to all workflows and data in this workspace.{' '}
|
||||
<span className='text-[var(--text-error)]'>This action cannot be undone.</span>
|
||||
</p>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<Button
|
||||
variant='default'
|
||||
onClick={() => setIsLeaveModalOpen(false)}
|
||||
disabled={isLeaving}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button variant='destructive' onClick={handleLeaveWorkspace} disabled={isLeaving}>
|
||||
{isLeaving ? 'Leaving...' : 'Leave Workspace'}
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ import {
|
||||
type GetBlockUpstreamReferencesResultType,
|
||||
} from '@/lib/copilot/tools/shared/schemas'
|
||||
import { BlockPathCalculator } from '@/lib/workflows/blocks/block-path-calculator'
|
||||
import { isValidStartBlockType } from '@/lib/workflows/triggers/start-block-types'
|
||||
import { useWorkflowRegistry } from '@/stores/workflows/registry/store'
|
||||
import { useWorkflowStore } from '@/stores/workflows/workflow/store'
|
||||
import type { Loop, Parallel } from '@/stores/workflows/workflow/types'
|
||||
@@ -140,9 +141,7 @@ export class GetBlockUpstreamReferencesClientTool extends BaseClientTool {
|
||||
const accessibleIds = new Set<string>(ancestorIds)
|
||||
accessibleIds.add(blockId)
|
||||
|
||||
const starterBlock = Object.values(blocks).find(
|
||||
(b) => b.type === 'starter' || b.type === 'start_trigger'
|
||||
)
|
||||
const starterBlock = Object.values(blocks).find((b) => isValidStartBlockType(b.type))
|
||||
if (starterBlock && ancestorIds.includes(starterBlock.id)) {
|
||||
accessibleIds.add(starterBlock.id)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { z } from 'zod'
|
||||
import { normalizeInputFormatValue } from '@/lib/workflows/input-format'
|
||||
import { isValidStartBlockType } from '@/lib/workflows/triggers/trigger-utils'
|
||||
import { isValidStartBlockType } from '@/lib/workflows/triggers/start-block-types'
|
||||
import type { InputFormatField } from '@/lib/workflows/types'
|
||||
import type { McpToolSchema } from './types'
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { isValidStartBlockType } from '@/lib/workflows/triggers/start-block-types'
|
||||
import type { InputFormatField } from '@/lib/workflows/types'
|
||||
|
||||
/**
|
||||
@@ -23,7 +24,7 @@ export function extractInputFieldsFromBlocks(
|
||||
// Find trigger block
|
||||
const triggerEntry = Object.entries(blocks).find(([, block]) => {
|
||||
const b = block as Record<string, unknown>
|
||||
return b.type === 'start_trigger' || b.type === 'input_trigger' || b.type === 'starter'
|
||||
return typeof b.type === 'string' && isValidStartBlockType(b.type)
|
||||
})
|
||||
|
||||
if (!triggerEntry) return []
|
||||
|
||||
21
apps/sim/lib/workflows/triggers/start-block-types.ts
Normal file
21
apps/sim/lib/workflows/triggers/start-block-types.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* Valid start block types that can trigger a workflow
|
||||
* This module is kept lightweight with no dependencies to avoid circular imports
|
||||
*/
|
||||
export const VALID_START_BLOCK_TYPES = [
|
||||
'starter',
|
||||
'start',
|
||||
'start_trigger',
|
||||
'api',
|
||||
'api_trigger',
|
||||
'input_trigger',
|
||||
] as const
|
||||
|
||||
export type ValidStartBlockType = (typeof VALID_START_BLOCK_TYPES)[number]
|
||||
|
||||
/**
|
||||
* Check if a block type is a valid start block type
|
||||
*/
|
||||
export function isValidStartBlockType(blockType: string): blockType is ValidStartBlockType {
|
||||
return VALID_START_BLOCK_TYPES.includes(blockType as ValidStartBlockType)
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { isValidStartBlockType } from '@/lib/workflows/triggers/start-block-types'
|
||||
import {
|
||||
type StartBlockCandidate,
|
||||
StartBlockPath,
|
||||
@@ -11,27 +12,6 @@ import { getTrigger } from '@/triggers'
|
||||
|
||||
const logger = createLogger('TriggerUtils')
|
||||
|
||||
/**
|
||||
* Valid start block types that can trigger a workflow
|
||||
*/
|
||||
export const VALID_START_BLOCK_TYPES = [
|
||||
'starter',
|
||||
'start',
|
||||
'start_trigger',
|
||||
'api',
|
||||
'api_trigger',
|
||||
'input_trigger',
|
||||
] as const
|
||||
|
||||
export type ValidStartBlockType = (typeof VALID_START_BLOCK_TYPES)[number]
|
||||
|
||||
/**
|
||||
* Check if a block type is a valid start block type
|
||||
*/
|
||||
export function isValidStartBlockType(blockType: string): blockType is ValidStartBlockType {
|
||||
return VALID_START_BLOCK_TYPES.includes(blockType as ValidStartBlockType)
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a workflow state has a valid start block
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user