fix(webhook-ui): fixed webhook ui (#1301)

* update infra and remove railway

* fix(webhook-ui): fixed webhook ui

* Revert "update infra and remove railway"

This reverts commit 88669ad0b7.

* feat(control-bar): updated export controls and webhook settings

* additional styling improvements to chat deploy & templates modals

* fix test event

---------

Co-authored-by: Emir Karabeg <emirkarabeg@berkeley.edu>
Co-authored-by: Vikhyath Mondreti <vikhyath@simstudio.ai>
This commit is contained in:
Waleed
2025-09-10 09:35:28 -07:00
committed by GitHub
parent 4d973ffb01
commit afb99fbaf1
8 changed files with 928 additions and 538 deletions

View File

@@ -124,17 +124,15 @@ export async function POST(request: NextRequest, { params }: { params: Promise<{
if (webhook.includeRateLimits) {
;(payload.data as any).rateLimits = {
workflowExecutionRateLimit: {
sync: {
limit: 60,
remaining: 45,
resetAt: new Date(timestamp + 60000).toISOString(),
},
async: {
limit: 60,
remaining: 50,
resetAt: new Date(timestamp + 60000).toISOString(),
},
sync: {
limit: 150,
remaining: 45,
resetAt: new Date(timestamp + 60000).toISOString(),
},
async: {
limit: 1000,
remaining: 50,
resetAt: new Date(timestamp + 60000).toISOString(),
},
}
}
@@ -149,12 +147,13 @@ export async function POST(request: NextRequest, { params }: { params: Promise<{
}
const body = JSON.stringify(payload)
const deliveryId = `delivery_test_${uuidv4()}`
const headers: Record<string, string> = {
'Content-Type': 'application/json',
'sim-event': 'workflow.execution.completed',
'sim-timestamp': timestamp.toString(),
'sim-delivery-id': `delivery_test_${uuidv4()}`,
'Idempotency-Key': `delivery_test_${uuidv4()}`,
'sim-delivery-id': deliveryId,
'Idempotency-Key': deliveryId,
}
if (webhook.secret) {

View File

@@ -272,12 +272,14 @@ export function ChatDeploy({
</span>
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel disabled={isDeleting}>Cancel</AlertDialogCancel>
<AlertDialogFooter className='flex'>
<AlertDialogCancel className='h-9 w-full rounded-[8px]' disabled={isDeleting}>
Cancel
</AlertDialogCancel>
<AlertDialogAction
onClick={handleDelete}
disabled={isDeleting}
className='bg-destructive hover:bg-destructive/90'
className='h-9 w-full rounded-[8px] bg-red-500 text-white transition-all duration-200 hover:bg-red-600 dark:bg-red-500 dark:hover:bg-red-600'
>
{isDeleting ? (
<span className='flex items-center'>
@@ -330,6 +332,7 @@ export function ChatDeploy({
onChange={(e) => updateField('title', e.target.value)}
required
disabled={chatSubmitting}
className='h-10 rounded-[8px]'
/>
{errors.title && <p className='text-destructive text-sm'>{errors.title}</p>}
</div>
@@ -344,11 +347,12 @@ export function ChatDeploy({
onChange={(e) => updateField('description', e.target.value)}
rows={3}
disabled={chatSubmitting}
className='min-h-[80px] resize-none rounded-[8px]'
/>
</div>
<div className='space-y-2'>
<Label className='font-medium text-sm'>Chat Output</Label>
<Card className='rounded-md border-input shadow-none'>
<Card className='rounded-[8px] border-input shadow-none'>
<CardContent className='p-1'>
<OutputSelect
workflowId={workflowId}
@@ -389,6 +393,7 @@ export function ChatDeploy({
onChange={(e) => updateField('welcomeMessage', e.target.value)}
rows={3}
disabled={chatSubmitting}
className='min-h-[80px] resize-none rounded-[8px]'
/>
<p className='text-muted-foreground text-xs'>
This message will be displayed when users first open the chat
@@ -445,12 +450,14 @@ export function ChatDeploy({
</span>
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel disabled={isDeleting}>Cancel</AlertDialogCancel>
<AlertDialogFooter className='flex'>
<AlertDialogCancel className='h-9 w-full rounded-[8px]' disabled={isDeleting}>
Cancel
</AlertDialogCancel>
<AlertDialogAction
onClick={handleDelete}
disabled={isDeleting}
className='bg-destructive hover:bg-destructive/90'
className='h-9 w-full rounded-[8px] bg-red-500 text-white transition-all duration-200 hover:bg-red-600 dark:bg-red-500 dark:hover:bg-red-600'
>
{isDeleting ? (
<span className='flex items-center'>

View File

@@ -1,7 +1,7 @@
import { useState } from 'react'
import { Check, Copy, Eye, EyeOff, Plus, RefreshCw, Trash2 } from 'lucide-react'
import { Button, Card, CardContent, Input, Label } from '@/components/ui'
import { cn } from '@/lib/utils'
import { cn, generatePassword } from '@/lib/utils'
import type { AuthType } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/control-bar/components/deploy-modal/components/chat-deploy/hooks/use-chat-form'
interface AuthSelectorProps {
@@ -32,16 +32,9 @@ export function AuthSelector({
const [emailError, setEmailError] = useState('')
const [copySuccess, setCopySuccess] = useState(false)
const generatePassword = () => {
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()_-+='
let result = ''
const length = 24
for (let i = 0; i < length; i++) {
result += chars.charAt(Math.floor(Math.random() * chars.length))
}
onPasswordChange(result)
const handleGeneratePassword = () => {
const password = generatePassword(24)
onPasswordChange(password)
}
const copyToClipboard = (text: string) => {
@@ -80,7 +73,7 @@ export function AuthSelector({
<Card
key={type}
className={cn(
'cursor-pointer overflow-hidden shadow-none transition-colors hover:bg-accent/30',
'cursor-pointer overflow-hidden rounded-[8px] shadow-none transition-all duration-200 hover:bg-accent/30',
authType === type
? 'border border-muted-foreground hover:bg-accent/50'
: 'border border-input'
@@ -113,7 +106,7 @@ export function AuthSelector({
{/* Auth Settings */}
{authType === 'password' && (
<Card className='shadow-none'>
<Card className='rounded-[8px] shadow-none'>
<CardContent className='p-4'>
<h3 className='mb-2 font-medium text-sm'>Password Settings</h3>
@@ -137,42 +130,70 @@ export function AuthSelector({
value={password}
onChange={(e) => onPasswordChange(e.target.value)}
disabled={disabled}
className='pr-28'
className='h-10 rounded-[8px] pr-32'
required={!isExistingChat}
autoComplete='new-password'
/>
<div className='absolute top-0 right-0 flex h-full'>
<div className='absolute top-0.5 right-0.5 flex h-9 items-center gap-1 pr-1'>
<Button
type='button'
variant='ghost'
size='icon'
onClick={generatePassword}
size='sm'
onClick={handleGeneratePassword}
disabled={disabled}
className='px-2'
className={cn(
'group h-7 w-7 rounded-md p-0',
'text-muted-foreground/60 transition-all duration-200',
'hover:scale-105 hover:bg-muted/50 hover:text-foreground',
'active:scale-95',
'disabled:cursor-not-allowed disabled:opacity-50',
'focus-visible:ring-2 focus-visible:ring-muted-foreground/20 focus-visible:ring-offset-1'
)}
>
<RefreshCw className='h-4 w-4' />
<RefreshCw className='h-3.5 w-3.5 transition-transform duration-200 group-hover:rotate-90' />
<span className='sr-only'>Generate password</span>
</Button>
<Button
type='button'
variant='ghost'
size='icon'
size='sm'
onClick={() => copyToClipboard(password)}
disabled={!password || disabled}
className='px-2'
className={cn(
'group h-7 w-7 rounded-md p-0',
'text-muted-foreground/60 transition-all duration-200',
'hover:scale-105 hover:bg-muted/50 hover:text-foreground',
'active:scale-95',
'disabled:cursor-not-allowed disabled:opacity-30',
'focus-visible:ring-2 focus-visible:ring-muted-foreground/20 focus-visible:ring-offset-1'
)}
>
{copySuccess ? <Check className='h-4 w-4' /> : <Copy className='h-4 w-4' />}
{copySuccess ? (
<Check className='h-3.5 w-3.5 text-foreground' />
) : (
<Copy className='h-3.5 w-3.5 transition-transform duration-200 group-hover:scale-110' />
)}
<span className='sr-only'>Copy password</span>
</Button>
<Button
type='button'
variant='ghost'
size='icon'
size='sm'
onClick={() => setShowPassword(!showPassword)}
disabled={disabled}
className='px-2'
className={cn(
'group h-7 w-7 rounded-md p-0',
'text-muted-foreground/60 transition-all duration-200',
'hover:scale-105 hover:bg-muted/50 hover:text-foreground',
'active:scale-95',
'focus-visible:ring-2 focus-visible:ring-muted-foreground/20 focus-visible:ring-offset-1'
)}
>
{showPassword ? <EyeOff className='h-4 w-4' /> : <Eye className='h-4 w-4' />}
{showPassword ? (
<EyeOff className='h-3.5 w-3.5 transition-transform duration-200 group-hover:scale-110' />
) : (
<Eye className='h-3.5 w-3.5 transition-transform duration-200 group-hover:scale-110' />
)}
<span className='sr-only'>
{showPassword ? 'Hide password' : 'Show password'}
</span>
@@ -190,7 +211,7 @@ export function AuthSelector({
)}
{authType === 'email' && (
<Card className='shadow-none'>
<Card className='rounded-[8px] shadow-none'>
<CardContent className='p-4'>
<h3 className='mb-2 font-medium text-sm'>Email Access Settings</h3>
@@ -200,7 +221,7 @@ export function AuthSelector({
value={newEmail}
onChange={(e) => setNewEmail(e.target.value)}
disabled={disabled}
className='flex-1'
className='h-10 flex-1 rounded-[8px]'
onKeyDown={(e) => {
if (e.key === 'Enter') {
e.preventDefault()
@@ -212,7 +233,7 @@ export function AuthSelector({
type='button'
onClick={handleAddEmail}
disabled={!newEmail.trim() || disabled}
className='shrink-0'
className='h-10 shrink-0 rounded-[8px]'
>
<Plus className='h-4 w-4' />
Add
@@ -253,7 +274,7 @@ export function AuthSelector({
)}
{authType === 'public' && (
<Card className='shadow-none'>
<Card className='rounded-[8px] shadow-none'>
<CardContent className='p-4'>
<h3 className='mb-2 font-medium text-sm'>Public Access Settings</h3>
<p className='text-muted-foreground text-xs'>

View File

@@ -156,7 +156,7 @@ export function ExampleCommand({
onClick={() => setMode('sync')}
className={`h-6 min-w-[50px] px-2 py-1 text-xs transition-none ${
mode === 'sync'
? 'border-primary bg-primary text-muted-foreground hover:border-primary hover:bg-primary hover:text-muted-foreground'
? 'border-primary bg-primary text-primary-foreground hover:border-primary hover:bg-primary hover:text-primary-foreground'
: ''
}`}
>
@@ -168,7 +168,7 @@ export function ExampleCommand({
onClick={() => setMode('async')}
className={`h-6 min-w-[50px] px-2 py-1 text-xs transition-none ${
mode === 'async'
? 'border-primary bg-primary text-muted-foreground hover:border-primary hover:bg-primary hover:text-muted-foreground'
? 'border-primary bg-primary text-primary-foreground hover:border-primary hover:bg-primary hover:text-primary-foreground'
: ''
}`}
>

View File

@@ -81,7 +81,7 @@ export function ExportControls({ disabled = false }: ExportControlsProps) {
<TooltipTrigger asChild>
{isDisabled ? (
<div className='inline-flex h-12 w-12 cursor-not-allowed items-center justify-center rounded-[11px] border bg-card text-card-foreground opacity-50 shadow-xs transition-colors'>
<Upload className='h-5 w-5' />
<Upload className='h-4 w-4' />
</div>
) : (
<Button

View File

@@ -360,7 +360,12 @@ export function TemplateModal({ open, onOpenChange, workflowId }: TemplateModalP
<Button
variant='ghost'
size='icon'
className='h-8 w-8 p-0'
className={cn(
'h-8 w-8 rounded-md p-0 text-muted-foreground/70 transition-all duration-200',
'hover:scale-105 hover:bg-muted/50 hover:text-foreground',
'active:scale-95',
'focus-visible:ring-2 focus-visible:ring-muted-foreground/20 focus-visible:ring-offset-1'
)}
onClick={() => onOpenChange(false)}
>
<X className='h-4 w-4' />
@@ -374,7 +379,7 @@ export function TemplateModal({ open, onOpenChange, workflowId }: TemplateModalP
onSubmit={form.handleSubmit(onSubmit)}
className='flex flex-1 flex-col overflow-hidden'
>
<div className='flex-1 overflow-y-auto px-6 py-6'>
<div className='flex-1 overflow-y-auto px-6 py-4'>
{isLoadingTemplate ? (
<div className='space-y-6'>
{/* Icon and Color row */}
@@ -414,7 +419,7 @@ export function TemplateModal({ open, onOpenChange, workflowId }: TemplateModalP
</div>
</div>
) : (
<div className='space-y-6'>
<div className='space-y-5'>
<div className='flex gap-3'>
<FormField
control={form.control}
@@ -426,11 +431,15 @@ export function TemplateModal({ open, onOpenChange, workflowId }: TemplateModalP
</FormLabel>
<Popover open={iconPopoverOpen} onOpenChange={setIconPopoverOpen}>
<PopoverTrigger asChild>
<Button variant='outline' role='combobox' className='h-10 w-20 p-0'>
<Button
variant='outline'
role='combobox'
className='h-10 w-20 rounded-[8px] border-border/50 p-0 transition-all duration-200 hover:border-border hover:bg-muted/50'
>
<SelectedIconComponent className='h-4 w-4' />
</Button>
</PopoverTrigger>
<PopoverContent className='z-50 w-84 p-0' align='start'>
<PopoverContent className='z-50 w-84 rounded-[8px] p-0' align='start'>
<div className='p-3'>
<div className='grid max-h-80 grid-cols-8 gap-2 overflow-y-auto'>
{icons.map((icon) => {
@@ -444,9 +453,10 @@ export function TemplateModal({ open, onOpenChange, workflowId }: TemplateModalP
setIconPopoverOpen(false)
}}
className={cn(
'flex h-8 w-8 items-center justify-center rounded-md border transition-colors hover:bg-muted',
'flex h-8 w-8 items-center justify-center rounded-md border border-border/40 transition-all duration-200',
'hover:scale-105 hover:border-border hover:bg-muted/50 active:scale-95',
field.value === icon.value &&
'bg-primary text-muted-foreground'
'border-primary/30 bg-primary/10 text-primary'
)}
>
<IconComponent className='h-4 w-4' />
@@ -475,7 +485,7 @@ export function TemplateModal({ open, onOpenChange, workflowId }: TemplateModalP
value={field.value}
onChange={field.onChange}
onBlur={field.onBlur}
className='h-10 w-20'
className='h-10 w-20 rounded-[8px]'
/>
</FormControl>
<FormMessage />
@@ -491,7 +501,11 @@ export function TemplateModal({ open, onOpenChange, workflowId }: TemplateModalP
<FormItem>
<FormLabel className='!text-foreground font-medium text-sm'>Name</FormLabel>
<FormControl>
<Input placeholder='Enter template name' {...field} />
<Input
placeholder='Enter template name'
className='h-10 rounded-[8px]'
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
@@ -508,7 +522,11 @@ export function TemplateModal({ open, onOpenChange, workflowId }: TemplateModalP
Author
</FormLabel>
<FormControl>
<Input placeholder='Enter author name' {...field} />
<Input
placeholder='Enter author name'
className='h-10 rounded-[8px]'
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
@@ -525,7 +543,7 @@ export function TemplateModal({ open, onOpenChange, workflowId }: TemplateModalP
</FormLabel>
<Select onValueChange={field.onChange} defaultValue={field.value}>
<FormControl>
<SelectTrigger>
<SelectTrigger className='h-10 rounded-[8px]'>
<SelectValue placeholder='Select a category' />
</SelectTrigger>
</FormControl>
@@ -554,7 +572,7 @@ export function TemplateModal({ open, onOpenChange, workflowId }: TemplateModalP
<FormControl>
<Textarea
placeholder='Describe what this template does...'
className='resize-none'
className='min-h-[80px] resize-none rounded-[8px]'
rows={3}
{...field}
/>
@@ -568,7 +586,7 @@ export function TemplateModal({ open, onOpenChange, workflowId }: TemplateModalP
</div>
{/* Fixed Footer */}
<div className='mt-auto border-t px-6 pt-4 pb-6'>
<div className='mt-auto border-t px-6 py-4'>
<div className='flex items-center'>
{existingTemplate && (
<Button
@@ -576,7 +594,7 @@ export function TemplateModal({ open, onOpenChange, workflowId }: TemplateModalP
variant='destructive'
onClick={() => setShowDeleteDialog(true)}
disabled={isSubmitting || isLoadingTemplate}
className='h-10 rounded-md px-4 py-2'
className='h-9 rounded-[8px] px-4'
>
Delete
</Button>
@@ -585,12 +603,11 @@ export function TemplateModal({ open, onOpenChange, workflowId }: TemplateModalP
type='submit'
disabled={isSubmitting || !isFormValid || isLoadingTemplate}
className={cn(
'ml-auto font-medium',
'ml-auto h-9 rounded-[8px] px-4 font-[480]',
'bg-[var(--brand-primary-hex)] hover:bg-[var(--brand-primary-hover-hex)]',
'shadow-[0_0_0_0_var(--brand-primary-hex)] hover:shadow-[0_0_0_4px_rgba(127,47,255,0.15)]',
'text-white transition-all duration-200',
'disabled:opacity-50 disabled:hover:bg-[var(--brand-primary-hex)] disabled:hover:shadow-none',
'h-10 rounded-md px-4 py-2'
'disabled:opacity-50 disabled:hover:bg-[var(--brand-primary-hex)] disabled:hover:shadow-none'
)}
>
{isSubmitting ? (
@@ -618,10 +635,12 @@ export function TemplateModal({ open, onOpenChange, workflowId }: TemplateModalP
undone.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel disabled={isDeleting}>Cancel</AlertDialogCancel>
<AlertDialogFooter className='flex'>
<AlertDialogCancel className='h-9 w-full rounded-[8px]' disabled={isDeleting}>
Cancel
</AlertDialogCancel>
<AlertDialogAction
className='bg-destructive text-destructive-foreground hover:bg-destructive/90'
className='h-9 w-full rounded-[8px] bg-red-500 text-white transition-all duration-200 hover:bg-red-600 dark:bg-red-500 dark:hover:bg-red-600'
disabled={isDeleting}
onClick={async () => {
if (!existingTemplate) return

View File

@@ -273,6 +273,22 @@ export function generateApiKey(): string {
return `sim_${nanoid(32)}`
}
/**
* Generates a secure random password
* @param length - The length of the password (default: 24)
* @returns A new secure password string
*/
export function generatePassword(length = 24): string {
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()_-+='
let result = ''
for (let i = 0; i < length; i++) {
result += chars.charAt(Math.floor(Math.random() * chars.length))
}
return result
}
/**
* Rotates through available API keys for a provider
* @param provider - The provider to get a key for (e.g., 'openai')