Skip to content

Commit 7fe6145

Browse files
committed
improvement(deploy): redesign workflow deployment experience
1 parent 375186c commit 7fe6145

18 files changed

Lines changed: 1100 additions & 921 deletions

File tree

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/deploy/components/deploy-modal/components/api/api.tsx

Lines changed: 39 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,7 @@
11
'use client'
22

33
import { useMemo, useState } from 'react'
4-
import {
5-
Button,
6-
ButtonGroup,
7-
ButtonGroupItem,
8-
Code,
9-
Combobox,
10-
Label,
11-
Skeleton,
12-
Tooltip,
13-
} from '@sim/emcn'
4+
import { Chip, ChipDropdown, ChipSwitch, Code, Label, Skeleton, Tooltip } from '@sim/emcn'
145
import { Check, Clipboard } from '@sim/emcn/icons'
156
import {
167
AGENT_STREAM_PROTOCOL_HEADER_LABEL,
@@ -55,6 +46,17 @@ const LANGUAGE_LABELS: Record<CodeLanguage, string> = {
5546
typescript: 'TypeScript',
5647
}
5748

49+
const LANGUAGE_OPTIONS = (Object.keys(LANGUAGE_LABELS) as CodeLanguage[]).map((value) => ({
50+
value,
51+
label: LANGUAGE_LABELS[value],
52+
}))
53+
54+
const ASYNC_EXAMPLE_OPTIONS = [
55+
{ label: 'Execute Job', value: 'execute' },
56+
{ label: 'Check Status', value: 'status' },
57+
{ label: 'Rate Limits', value: 'rate-limits' },
58+
] as const
59+
5860
const LANGUAGE_SYNTAX: Record<CodeLanguage, 'python' | 'javascript' | 'json'> = {
5961
curl: 'javascript',
6062
python: 'python',
@@ -416,19 +418,6 @@ console.log(limits);`
416418
}
417419
}
418420

419-
const getAsyncExampleTitle = () => {
420-
switch (asyncExampleType) {
421-
case 'execute':
422-
return 'Start Execution'
423-
case 'status':
424-
return 'Check Status'
425-
case 'rate-limits':
426-
return 'Usage Limits'
427-
default:
428-
return 'Start Execution'
429-
}
430-
}
431-
432421
const handleCopy = (key: keyof CopiedState, value: string) => {
433422
navigator.clipboard.writeText(value)
434423
setCopied((prev) => ({ ...prev, [key]: true }))
@@ -460,28 +449,26 @@ console.log(limits);`
460449
<div className='mb-[6.5px] flex items-center justify-between'>
461450
<Label className='block pl-0.5 text-[var(--text-primary)] text-small'>Language</Label>
462451
</div>
463-
<ButtonGroup value={language} onValueChange={(val) => setLanguage(val as CodeLanguage)}>
464-
{(Object.keys(LANGUAGE_LABELS) as CodeLanguage[]).map((lang) => (
465-
<ButtonGroupItem key={lang} value={lang}>
466-
{LANGUAGE_LABELS[lang]}
467-
</ButtonGroupItem>
468-
))}
469-
</ButtonGroup>
452+
<ChipSwitch
453+
value={language}
454+
onChange={setLanguage}
455+
options={LANGUAGE_OPTIONS}
456+
aria-label='Code language'
457+
size='compact'
458+
/>
470459
</div>
471460

472461
<div>
473462
<div className='mb-[6.5px] flex items-center justify-between'>
474463
<Label className='block pl-0.5 text-[var(--text-primary)] text-small'>Run workflow</Label>
475464
<Tooltip.Root>
476465
<Tooltip.Trigger asChild>
477-
<Button
478-
variant='ghost'
466+
<Chip
467+
leftIcon={copied.sync ? Check : Clipboard}
479468
onClick={() => handleCopy('sync', getSyncCommand())}
480-
aria-label='Copy command'
481-
className='!p-1.5 -my-1.5'
482-
>
483-
{copied.sync ? <Check className='size-3' /> : <Clipboard className='size-3' />}
484-
</Button>
469+
aria-label={copied.sync ? 'Command copied' : 'Copy command'}
470+
className='-my-1.5'
471+
/>
485472
</Tooltip.Trigger>
486473
<Tooltip.Content>
487474
<span>{copied.sync ? 'Copied' : 'Copy'}</span>
@@ -504,14 +491,12 @@ console.log(limits);`
504491
<div className='flex items-center gap-1.5'>
505492
<Tooltip.Root>
506493
<Tooltip.Trigger asChild>
507-
<Button
508-
variant='ghost'
494+
<Chip
495+
leftIcon={copied.stream ? Check : Clipboard}
509496
onClick={() => handleCopy('stream', getStreamCommand())}
510-
aria-label='Copy command'
511-
className='!p-1.5 -my-1.5'
512-
>
513-
{copied.stream ? <Check className='size-3' /> : <Clipboard className='size-3' />}
514-
</Button>
497+
aria-label={copied.stream ? 'Command copied' : 'Copy command'}
498+
className='-my-1.5'
499+
/>
515500
</Tooltip.Trigger>
516501
<Tooltip.Content>
517502
<span>{copied.stream ? 'Copied' : 'Copy'}</span>
@@ -524,6 +509,8 @@ console.log(limits);`
524509
placeholder='Select outputs'
525510
valueMode='label'
526511
align='end'
512+
size='md'
513+
className='w-[140px]'
527514
/>
528515
</div>
529516
</div>
@@ -544,31 +531,23 @@ console.log(limits);`
544531
<div className='flex items-center gap-1.5'>
545532
<Tooltip.Root>
546533
<Tooltip.Trigger asChild>
547-
<Button
548-
variant='ghost'
534+
<Chip
535+
leftIcon={copied.async ? Check : Clipboard}
549536
onClick={() => handleCopy('async', getAsyncCommand())}
550-
aria-label='Copy command'
551-
className='!p-1.5 -my-1.5'
552-
>
553-
{copied.async ? <Check className='size-3' /> : <Clipboard className='size-3' />}
554-
</Button>
537+
aria-label={copied.async ? 'Command copied' : 'Copy command'}
538+
className='-my-1.5'
539+
/>
555540
</Tooltip.Trigger>
556541
<Tooltip.Content>
557542
<span>{copied.async ? 'Copied' : 'Copy'}</span>
558543
</Tooltip.Content>
559544
</Tooltip.Root>
560-
<Combobox
561-
size='sm'
562-
className='!w-fit !py-0.5 min-w-[100px] rounded-md px-[9px]'
563-
options={[
564-
{ label: 'Start Execution', value: 'execute' },
565-
{ label: 'Check Status', value: 'status' },
566-
{ label: 'Usage Limits', value: 'rate-limits' },
567-
]}
545+
<ChipDropdown
546+
className='w-[140px]'
547+
options={ASYNC_EXAMPLE_OPTIONS}
568548
value={asyncExampleType}
569549
onChange={(value) => setAsyncExampleType(value as AsyncExampleType)}
570550
align='end'
571-
dropdownWidth={160}
572551
/>
573552
</div>
574553
</div>

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/deploy/components/deploy-modal/components/chat/chat.tsx

Lines changed: 52 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,14 @@
22

33
import { useEffect, useRef, useState } from 'react'
44
import {
5-
ButtonGroup,
6-
ButtonGroupItem,
75
ChipConfirmModal,
86
ChipEmailsInput,
97
ChipInput,
10-
cn,
11-
Input,
8+
ChipSwitch,
9+
ChipTextarea,
1210
Label,
1311
Loader,
1412
Skeleton,
15-
Switch,
16-
Textarea,
1713
Tooltip,
1814
} from '@sim/emcn'
1915
import { Check, TriangleAlert } from '@sim/emcn/icons'
@@ -47,6 +43,10 @@ import {
4743
const logger = createLogger('ChatDeploy')
4844

4945
const IDENTIFIER_PATTERN = /^[a-z0-9-]+$/
46+
const BOOLEAN_OPTIONS = [
47+
{ value: 'off', label: 'Off' },
48+
{ value: 'on', label: 'On' },
49+
] as const
5050

5151
interface ChatDeployProps {
5252
workflowId: string
@@ -402,11 +402,13 @@ export function ChatDeploy({
402402
Include thinking
403403
</Label>
404404
</div>
405-
<Switch
406-
checked={formData.includeThinking}
405+
<ChipSwitch
406+
value={formData.includeThinking ? 'on' : 'off'}
407+
options={BOOLEAN_OPTIONS}
407408
disabled={chatSubmitting}
408-
onCheckedChange={(checked) => updateField('includeThinking', checked)}
409+
onChange={(value) => updateField('includeThinking', value === 'on')}
409410
aria-label='Include thinking'
411+
size='compact'
410412
/>
411413
</div>
412414

@@ -416,11 +418,13 @@ export function ChatDeploy({
416418
Include tool calls
417419
</Label>
418420
</div>
419-
<Switch
420-
checked={formData.includeToolCalls}
421+
<ChipSwitch
422+
value={formData.includeToolCalls ? 'on' : 'off'}
423+
options={BOOLEAN_OPTIONS}
421424
disabled={chatSubmitting}
422-
onCheckedChange={(checked) => updateField('includeToolCalls', checked)}
425+
onChange={(value) => updateField('includeToolCalls', value === 'on')}
423426
aria-label='Include tool calls'
427+
size='compact'
424428
/>
425429
</div>
426430

@@ -446,7 +450,7 @@ export function ChatDeploy({
446450
>
447451
Welcome message
448452
</Label>
449-
<Textarea
453+
<ChipTextarea
450454
id='welcomeMessage'
451455
placeholder='Enter a welcome message for your chat'
452456
value={formData.welcomeMessage}
@@ -589,50 +593,35 @@ function IdentifierInput({
589593
>
590594
URL
591595
</Label>
592-
<div
593-
className={cn(
594-
'relative flex items-stretch overflow-hidden rounded-sm border border-[var(--border-1)] bg-[var(--surface-5)]',
595-
error && 'border-[var(--text-error)]'
596-
)}
597-
>
598-
<div className='flex items-center whitespace-nowrap bg-[var(--surface-5)] pr-1.5 pl-2 text-[var(--text-secondary)] text-sm'>
599-
{getDomainPrefix()}
600-
</div>
601-
<div className='relative flex-1'>
602-
<Input
603-
id='chat-url'
604-
placeholder='my-chat'
605-
value={value}
606-
onChange={(e) => handleChange(e.target.value)}
607-
required
608-
disabled={disabled}
609-
className={cn(
610-
'rounded-none border-0 bg-transparent pl-0 shadow-none disabled:bg-transparent disabled:opacity-100',
611-
(isChecking || (isValid && value)) && 'pr-8'
612-
)}
613-
/>
614-
{isChecking ? (
615-
<div className='-translate-y-1/2 absolute top-1/2 right-2'>
616-
<Loader className='size-4 text-[var(--text-tertiary)]' animate />
617-
</div>
618-
) : (
619-
isValid &&
620-
value &&
621-
value !== originalIdentifier && (
622-
<Tooltip.Root>
623-
<Tooltip.Trigger asChild>
624-
<div className='-translate-y-1/2 absolute top-1/2 right-2'>
625-
<Check className='size-4 text-[var(--brand-accent)]' />
626-
</div>
627-
</Tooltip.Trigger>
628-
<Tooltip.Content>
629-
<span>Name is available</span>
630-
</Tooltip.Content>
631-
</Tooltip.Root>
632-
)
633-
)}
634-
</div>
635-
</div>
596+
<ChipInput
597+
id='chat-url'
598+
placeholder='my-chat'
599+
value={value}
600+
onChange={(event) => handleChange(event.target.value)}
601+
required
602+
disabled={disabled}
603+
error={Boolean(error)}
604+
aria-invalid={Boolean(error)}
605+
startAdornment={
606+
<span className='whitespace-nowrap text-[var(--text-muted)] text-sm'>
607+
{getDomainPrefix()}
608+
</span>
609+
}
610+
endAdornment={
611+
isChecking ? (
612+
<Loader className='size-[14px] shrink-0 text-[var(--text-icon)]' animate />
613+
) : isValid && value && value !== originalIdentifier ? (
614+
<Tooltip.Root>
615+
<Tooltip.Trigger asChild>
616+
<span className='shrink-0' aria-label='Name is available'>
617+
<Check className='size-[14px] text-[var(--brand-accent)]' />
618+
</span>
619+
</Tooltip.Trigger>
620+
<Tooltip.Content>Name is available</Tooltip.Content>
621+
</Tooltip.Root>
622+
) : null
623+
}
624+
/>
636625
{error && <p className='mt-[6.5px] text-[var(--text-error)] text-caption'>{error}</p>}
637626
<p className='mt-[6.5px] truncate text-[var(--text-secondary)] text-xs'>
638627
{isEditingExisting && value ? (
@@ -729,17 +718,14 @@ function AuthSelector({
729718
<Label className='mb-[6.5px] block pl-0.5 text-[var(--text-primary)] text-small'>
730719
Access control
731720
</Label>
732-
<ButtonGroup
721+
<ChipSwitch
733722
value={authType}
734-
onValueChange={(val) => onAuthTypeChange(val as AuthType)}
723+
onChange={onAuthTypeChange}
724+
options={authOptions.map((type) => ({ value: type, label: AUTH_LABELS[type] }))}
735725
disabled={disabled}
736-
>
737-
{authOptions.map((type) => (
738-
<ButtonGroupItem key={type} value={type}>
739-
{AUTH_LABELS[type]}
740-
</ButtonGroupItem>
741-
))}
742-
</ButtonGroup>
726+
aria-label='Access control'
727+
size='compact'
728+
/>
743729
</div>
744730

745731
{authType === 'password' && (

0 commit comments

Comments
 (0)