diff --git a/apps/realtime/src/database/operations.ts b/apps/realtime/src/database/operations.ts index f28543a8004..aaf932ff86e 100644 --- a/apps/realtime/src/database/operations.ts +++ b/apps/realtime/src/database/operations.ts @@ -605,6 +605,33 @@ async function handleBlockOperationTx( break } + case BLOCK_OPERATIONS.UPDATE_DESCRIPTION: { + if (!payload.id || payload.description === undefined) { + throw new Error('Missing required fields for update description operation') + } + + const updateResult = await tx + .update(workflowBlocks) + .set({ + data: sql`jsonb_set( + coalesce(${workflowBlocks.data}, '{}'::jsonb), + '{description}', + ${JSON.stringify(payload.description)}::jsonb, + true + )`, + updatedAt: new Date(), + }) + .where(and(eq(workflowBlocks.id, payload.id), eq(workflowBlocks.workflowId, workflowId))) + .returning({ id: workflowBlocks.id }) + + if (updateResult.length === 0) { + throw new Error(`Block ${payload.id} not found in workflow ${workflowId}`) + } + + logger.debug(`Updated block description: ${payload.id}`) + break + } + case BLOCK_OPERATIONS.TOGGLE_ENABLED: { if (!payload.id) { throw new Error('Missing block ID for toggle enabled operation') diff --git a/apps/realtime/src/middleware/permissions.test.ts b/apps/realtime/src/middleware/permissions.test.ts index 4edc05d795a..51e640f8345 100644 --- a/apps/realtime/src/middleware/permissions.test.ts +++ b/apps/realtime/src/middleware/permissions.test.ts @@ -274,6 +274,12 @@ describe('checkRolePermission', () => { { operation: 'update', adminAllowed: true, writeAllowed: true, readAllowed: false }, { operation: 'update-position', adminAllowed: true, writeAllowed: true, readAllowed: false }, { operation: 'update-name', adminAllowed: true, writeAllowed: true, readAllowed: false }, + { + operation: 'update-description', + adminAllowed: true, + writeAllowed: true, + readAllowed: false, + }, { operation: 'toggle-enabled', adminAllowed: true, writeAllowed: true, readAllowed: false }, { operation: 'update-parent', adminAllowed: true, writeAllowed: true, readAllowed: false }, { diff --git a/apps/realtime/src/middleware/permissions.ts b/apps/realtime/src/middleware/permissions.ts index e17678461a6..b004135d016 100644 --- a/apps/realtime/src/middleware/permissions.ts +++ b/apps/realtime/src/middleware/permissions.ts @@ -26,6 +26,7 @@ const WRITE_OPERATIONS: string[] = [ // Block operations BLOCK_OPERATIONS.UPDATE_POSITION, BLOCK_OPERATIONS.UPDATE_NAME, + BLOCK_OPERATIONS.UPDATE_DESCRIPTION, BLOCK_OPERATIONS.TOGGLE_ENABLED, BLOCK_OPERATIONS.UPDATE_PARENT, BLOCK_OPERATIONS.UPDATE_ADVANCED_MODE, diff --git a/apps/sim/app/layout.tsx b/apps/sim/app/layout.tsx index 0bdb20393fa..9b1ddef0599 100644 --- a/apps/sim/app/layout.tsx +++ b/apps/sim/app/layout.tsx @@ -8,12 +8,7 @@ import { PasteAdmissionGuard } from '@/app/_shell/paste-admission-guard' import { PostHogProvider } from '@/app/_shell/providers/posthog-provider' import { generateBrandedMetadata, generateThemeCSS } from '@/ee/whitelabeling' import '@/app/_styles/globals.css' -import { - isChatEnabled, - isHosted, - isReactGrabEnabled, - isReactScanEnabled, -} from '@/lib/core/config/env-flags' +import { isHosted, isReactGrabEnabled, isReactScanEnabled } from '@/lib/core/config/env-flags' import { ConsentProvider } from '@/app/_shell/consent/consent-provider' import { DesktopUpdateGate } from '@/app/_shell/desktop-update-gate' import { HydrationErrorHandler } from '@/app/_shell/hydration-error-handler' @@ -171,10 +166,9 @@ export default function RootLayout({ children }: { children: React.ReactNode }) } var activeTab = panelState && panelState.activeTab; - // A session that used the Chat tab before it was turned off still - // has 'copilot' persisted; without this the CSS hides every tab - // body and the panel paints empty. - if (activeTab === 'copilot' && !${isChatEnabled}) { + // Chat moved out of the right inspector. Migrate the legacy + // persisted tab before first paint so the inspector opens on Blocks. + if (activeTab === 'copilot') { activeTab = 'toolbar'; } if (activeTab) { @@ -243,7 +237,10 @@ export default function RootLayout({ children }: { children: React.ReactNode }) {isHosted ? : } - + diff --git a/apps/sim/app/playground/page.tsx b/apps/sim/app/playground/page.tsx index 493ddeb8be3..3e92ddf33e8 100644 --- a/apps/sim/app/playground/page.tsx +++ b/apps/sim/app/playground/page.tsx @@ -13,7 +13,15 @@ import { ButtonGroupItem, Checkbox, ChevronDown, + ChipCombobox, + ChipCopyInput, ChipDatePicker, + ChipInput, + ChipSelect, + ChipSwitch, + ChipTag, + ChipTextarea, + ChipTimePicker, Code, Combobox, Connections, @@ -82,7 +90,7 @@ import { ZoomIn, ZoomOut, } from '@sim/emcn' -import { ArrowLeft, Folder, Moon, Sun } from '@sim/emcn/icons' +import { ArrowLeft, Folder, Moon, Search, Sun } from '@sim/emcn/icons' import { notFound, useRouter } from 'next/navigation' import { env, isTruthy } from '@/lib/core/config/env' @@ -106,6 +114,31 @@ function VariantRow({ label, children }: { label: string; children: React.ReactN ) } +interface WorkflowFieldPreviewProps { + title: string + hint?: string + required?: boolean + children: React.ReactNode +} + +function WorkflowFieldPreview({ + title, + hint, + required = false, + children, +}: WorkflowFieldPreviewProps) { + return ( +
+ + {children} + {hint ?

{hint}

: null} +
+ ) +} + const SAMPLE_CODE = `function greet(name) { console.log("Hello, " + name); return { success: true }; @@ -121,6 +154,11 @@ const COMBOBOX_OPTIONS = [ { label: 'Option 3', value: 'opt3' }, ] +const WORKFLOW_BOOLEAN_OPTIONS = [ + { value: 'off', label: 'Off' }, + { value: 'on', label: 'On' }, +] as const + const DARK_MODE_EVENT = 'playground:dark-mode-change' const subscribeToDarkMode = (onStoreChange: () => void) => { @@ -134,6 +172,15 @@ const getServerDarkModeSnapshot = () => false export default function PlaygroundPage() { const router = useRouter() const [comboboxValue, setComboboxValue] = useState('') + const [workflowTextValue, setWorkflowTextValue] = useState('claude-sonnet-5') + const [workflowDescription, setWorkflowDescription] = useState( + 'Classify each support request and return the urgency, owner, and next action.' + ) + const [workflowChoice, setWorkflowChoice] = useState('opt1') + const [workflowSearchChoice, setWorkflowSearchChoice] = useState('opt2') + const [workflowMultiChoices, setWorkflowMultiChoices] = useState(['opt1', 'opt3']) + const [workflowToggle, setWorkflowToggle] = useState<'off' | 'on'>('off') + const [workflowTime, setWorkflowTime] = useState('09:30') const [switchValue, setSwitchValue] = useState(false) const [checkboxValue, setCheckboxValue] = useState(false) const [sliderValue, setSliderValue] = useState([50]) @@ -193,6 +240,148 @@ export default function PlaygroundPage() {

+
+

+ The canonical workflow field language. These examples exercise shared EMCN chrome + before workflow-specific adapters add variables, references, and generated content. +

+
+ + setWorkflowTextValue(event.target.value)} + placeholder='Enter a value' + /> + + + + + + ⌘K} + /> + + + setWorkflowDescription(event.target.value)} + placeholder='Describe what this block should do' + /> + + + + + +
+ + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
+
+ {/* Toast */}
diff --git a/apps/sim/app/workspace/[workspaceId]/components/index.ts b/apps/sim/app/workspace/[workspaceId]/components/index.ts index 95adab9b7bb..4997ff649de 100644 --- a/apps/sim/app/workspace/[workspaceId]/components/index.ts +++ b/apps/sim/app/workspace/[workspaceId]/components/index.ts @@ -30,6 +30,7 @@ export type { ColumnOption, FilterConfig, FilterTag, + ResourceOptionsSize, SearchConfig, SearchTag, SortConfig, diff --git a/apps/sim/app/workspace/[workspaceId]/components/resource/components/resource-options/index.ts b/apps/sim/app/workspace/[workspaceId]/components/resource/components/resource-options/index.ts index 48603fa9448..e7faaa0f08d 100644 --- a/apps/sim/app/workspace/[workspaceId]/components/resource/components/resource-options/index.ts +++ b/apps/sim/app/workspace/[workspaceId]/components/resource/components/resource-options/index.ts @@ -2,6 +2,7 @@ export type { ColumnOption, FilterConfig, FilterTag, + ResourceOptionsSize, SearchConfig, SearchTag, SortConfig, diff --git a/apps/sim/app/workspace/[workspaceId]/components/resource/components/resource-options/resource-options.tsx b/apps/sim/app/workspace/[workspaceId]/components/resource/components/resource-options/resource-options.tsx index 6f30372b129..935ec709661 100644 --- a/apps/sim/app/workspace/[workspaceId]/components/resource/components/resource-options/resource-options.tsx +++ b/apps/sim/app/workspace/[workspaceId]/components/resource/components/resource-options/resource-options.tsx @@ -27,6 +27,22 @@ const SEARCH_ICON = ( const RESOURCE_MENU_EDGE_OFFSET = 6 +/** + * Edge fade for the compact filter-tag row. Transparent for the bar's own 12px + * gutter at each end, so a tag scrolled under it dissolves into the padding + * rather than stopping at a hard line. + */ +const TAG_ROW_FADE = + 'linear-gradient(to right, transparent 0, black 12px, black calc(100% - 12px), transparent 100%)' + +/** + * Control scale for the bar. `md` is the page default; `sm` is for the narrow + * surfaces — the editor panel — where the page-scale controls eat the width the + * list needs. Only the chrome shrinks: the same controls, in the same order, + * doing the same things. + */ +export type ResourceOptionsSize = 'sm' | 'md' + /** * Section heading inside a filter popover ("File Type", "Owner", …). One constant so every * resource list labels its filter sections identically — muted at the field-title size, per @@ -66,6 +82,12 @@ export interface SearchConfig { value: string onChange: (value: string) => void placeholder?: string + /** + * Drops the leading magnifier. For surfaces that already carry a search + * affordance directly above this bar, where a second one reads as two + * different searches rather than one. + */ + hideIcon?: boolean inputRef?: React.RefObject onKeyDown?: (e: React.KeyboardEvent) => void onFocus?: () => void @@ -114,6 +136,8 @@ interface ResourceOptionsProps { * so it is separated from the menu group rather than joined to it. */ trailing?: ReactNode + /** Control scale. Defaults to the page-scale `md`. */ + size?: ResourceOptionsSize } export const ResourceOptions = memo(function ResourceOptions({ @@ -124,6 +148,7 @@ export const ResourceOptions = memo(function ResourceOptions({ aside, asideEnd, trailing, + size = 'md', }: ResourceOptionsProps) { /** * Coordinates the Filter popover and Sort menu as a single menu bar: clicking @@ -136,6 +161,16 @@ export const ResourceOptions = memo(function ResourceOptions({ const isToggleFilter = filter?.mode === 'toggle' const popoverFilter = filter && filter.mode !== 'toggle' ? filter : null + /** + * Applied filters sit inline with Filter and Sort on a page-width bar, but on + * a narrow one every tag shoves those two controls sideways and squeezes the + * search. There they take their own row instead, scrolling sideways so the + * controls stay put however many filters are on. + */ + const stackTags = size === 'sm' + const hasTags = Boolean(filterTags && filterTags.length > 0) + const inlineTags = stackTags ? undefined : filterTags + const hasContent = search || sort || @@ -147,21 +182,31 @@ export const ResourceOptions = memo(function ResourceOptions({ if (!hasContent) return null return ( -
+
- {search && } + {search && } {/* `ml-auto` moves to `trailing` when present so the menu cluster stays put and only the trailing action is pushed to the far edge. */}
{aside}
- {filterTags?.map((tag) => ( - + {inlineTags?.map((tag) => ( + {tag.label} ))} {isToggleFilter && filter.mode === 'toggle' ? ( - + Filter ) : popoverFilter ? ( @@ -176,13 +221,14 @@ export const ResourceOptions = memo(function ResourceOptions({
- + Filter {sort && ( setOpenMenu((current) => @@ -209,20 +255,51 @@ export const ResourceOptions = memo(function ResourceOptions({ ) : null} - {sort && (isToggleFilter || !popoverFilter) && } + {sort && (isToggleFilter || !popoverFilter) && ( + + )}
{asideEnd}
{trailing &&
{trailing}
}
+ {stackTags && hasTags && ( + /* + * Masked at both ends rather than wrapped: a tag cut off mid-fade says + * there is more to scroll to, where a wrapped row silently grows the bar + * taller the more filters are on. The mask is symmetrical because the row + * can be scrolled away from either edge. + */ +
+
+ {filterTags?.map((tag) => ( + + {tag.label} + + ))} +
+
+ )}
) }) -const SearchSection = memo(function SearchSection({ search }: { search: SearchConfig }) { +const SearchSection = memo(function SearchSection({ + search, + size, +}: { + search: SearchConfig + size: ResourceOptionsSize +}) { return (
- {SEARCH_ICON} + {search.hideIcon ? null : SEARCH_ICON}
{search.tags?.map((tag, i) => (
{search.tags?.length || search.value ? ( @@ -273,6 +360,8 @@ const SearchSection = memo(function SearchSection({ search }: { search: SearchCo interface SortDropdownProps { config: SortConfig + /** Control scale, matching {@link ResourceOptionsProps.size}. */ + size?: ResourceOptionsSize /** Controlled open state — omit for standalone (uncontrolled) usage. */ open?: boolean /** Controlled open-change handler, paired with {@link SortDropdownProps.open}. */ @@ -281,6 +370,7 @@ interface SortDropdownProps { export const SortDropdown = memo(function SortDropdown({ config, + size = 'md', open, onOpenChange, }: SortDropdownProps) { @@ -289,7 +379,7 @@ export const SortDropdown = memo(function SortDropdown({ return ( - + Sort diff --git a/apps/sim/app/workspace/[workspaceId]/logs/components/log-details/components/execution-snapshot/execution-snapshot.tsx b/apps/sim/app/workspace/[workspaceId]/logs/components/log-details/components/execution-snapshot/execution-snapshot.tsx index 08d8c4095f2..480fec85038 100644 --- a/apps/sim/app/workspace/[workspaceId]/logs/components/log-details/components/execution-snapshot/execution-snapshot.tsx +++ b/apps/sim/app/workspace/[workspaceId]/logs/components/log-details/components/execution-snapshot/execution-snapshot.tsx @@ -45,8 +45,12 @@ interface ExecutionSnapshotProps { height?: string | number width?: string | number isModal?: boolean + showBorder?: boolean isOpen?: boolean onClose?: () => void + initialSelectedBlockId?: string | null + autoSelectLeftmost?: boolean + showBlockCloseButton?: boolean } export function ExecutionSnapshot({ @@ -56,8 +60,12 @@ export function ExecutionSnapshot({ height = '100%', width = '100%', isModal = false, + showBorder = !isModal, isOpen = false, onClose = () => {}, + initialSelectedBlockId, + autoSelectLeftmost = true, + showBlockCloseButton = !isModal, }: ExecutionSnapshotProps) { const { data, isLoading, error } = useExecutionSnapshot(executionId) const modalDescriptionId = useId() @@ -160,9 +168,10 @@ export function ExecutionSnapshot({ height={height} width={width} onCanvasContextMenu={handleCanvasContextMenu} - showBorder={!isModal} - autoSelectLeftmost - showBlockCloseButton={!isModal} + showBorder={showBorder} + initialSelectedBlockId={initialSelectedBlockId} + autoSelectLeftmost={autoSelectLeftmost} + showBlockCloseButton={showBlockCloseButton} /> ) } diff --git a/apps/sim/app/workspace/[workspaceId]/logs/components/log-details/log-details.tsx b/apps/sim/app/workspace/[workspaceId]/logs/components/log-details/log-details.tsx index 1d9348154b8..1325fe456ad 100644 --- a/apps/sim/app/workspace/[workspaceId]/logs/components/log-details/log-details.tsx +++ b/apps/sim/app/workspace/[workspaceId]/logs/components/log-details/log-details.tsx @@ -310,9 +310,14 @@ export type LogDetailsTab = 'overview' | 'trace' interface LogDetailsContentProps { log: WorkflowLogRow onActiveTabChange?: (tab: LogDetailsTab) => void + onViewSnapshot?: () => void } -export function LogDetailsContent({ log, onActiveTabChange }: LogDetailsContentProps) { +export function LogDetailsContent({ + log, + onActiveTabChange, + onViewSnapshot, +}: LogDetailsContentProps) { const [isExecutionSnapshotOpen, setIsExecutionSnapshotOpen] = useState(false) const [activeTab, setActiveTab] = useQueryState(logDetailsTabParam.key, { ...logDetailsTabParam.parser, @@ -600,7 +605,10 @@ export function LogDetailsContent({ log, onActiveTabChange }: LogDetailsContentP {showWorkflowState && (
Snapshot - setIsExecutionSnapshotOpen(true)}> + setIsExecutionSnapshotOpen(true))} + > View Snapshot
@@ -705,7 +713,7 @@ export function LogDetailsContent({ log, onActiveTabChange }: LogDetailsContentP
{/* Frozen Canvas Modal */} - {log.executionId && ( + {log.executionId && !onViewSnapshot && ( void /** Keeps the note and its swell selected while the portalled color menu is open. */ onNoteColorMenuOpen?: () => void + /** Opens documentation for the selected block from the inline editor menu. */ + onOpenDocs?: () => void } /** @@ -193,11 +210,13 @@ export const ActionBar = memo( blockType, disabled = false, variant = 'floating', + inlineActions = 'run', isRunning = false, isWorkflowRunning = false, noteColor = DEFAULT_NOTE_COLOR, onNoteColorChange, onNoteColorMenuOpen, + onOpenDocs, }: ActionBarProps) { const { collaborativeBatchAddBlocks, @@ -258,6 +277,7 @@ export const ActionBar = memo( const isResponseBlock = blockType === 'response' const isNoteBlock = blockType === 'note' const isInsideSubflow = parentId && (parentType === 'loop' || parentType === 'parallel') + const cantEnable = !isEnabled && isParentDisabled const { dependenciesSatisfied } = getRunFromBlockDependencyState(blockId, edges, snapshot) const canRunFromBlock = @@ -273,6 +293,7 @@ export const ActionBar = memo( const canStopWorkflow = isWorkflowRunning && !disabled const canRunBlock = !isWorkflowRunning && canRunFromBlock && !disabled && !isLocked && !isParentLocked + const [isInlineMenuOpen, setIsInlineMenuOpen] = useState(false) const isSwell = variant === 'swell' const firstActionId: ActionId = isNoteBlock ? 'color' @@ -400,6 +421,111 @@ export const ActionBar = memo( return defaultMessage } + if (variant === 'inline') { + if (inlineActions === 'menu') { + return ( + + + + + + + + {!isInlineMenuOpen && Block actions} + + + {!isNoteBlock && ( + collaborativeBatchToggleBlockEnabled([blockId])} + disabled={ + isWorkflowRunning || disabled || isLocked || isParentLocked || cantEnable + } + > + {isEnabled ? : } + {isEnabled ? 'Disable' : 'Enable'} + + )} + {userPermissions.canAdmin && ( + collaborativeBatchToggleLocked([blockId])} + disabled={isWorkflowRunning || disabled || (isLocked && isParentLocked)} + > + {isLocked ? : } + {isLocked ? 'Unlock' : 'Lock'} + + )} + {!isStartBlock && !isResponseBlock && ( + + + Duplicate + + )} + collaborativeBatchRemoveBlocks([blockId])} + disabled={isWorkflowRunning || disabled || isLocked || isParentLocked} + > + + Delete + + + + + Docs + + + + ) + } + + return ( + + + + + ) : undefined + } + onClick={(event) => { + event.stopPropagation() + if (canStopWorkflow) { + handleCancelExecution() + return + } + if (canRunBlock) handleRunFromBlockClick() + }} + disabled={!canStopWorkflow && !canRunBlock} + aria-label={isWorkflowRunning ? 'Stop workflow' : 'Run block'} + > + {isWorkflowRunning ? 'Stop' : 'Run block'} + + + + + {isWorkflowRunning + ? getTooltipMessage('Stop') + : isLocked || isParentLocked + ? 'Block is locked' + : !isEnabled || isParentDisabled + ? 'Block is disabled' + : !dependenciesSatisfied + ? 'Run previous blocks first' + : getTooltipMessage('Run block')} + + + ) + } + return (
({ }: ButtonHTMLAttributes & { variant?: string }) => ( ), + Chip: ({ + children, + className: _className, + variant: _variant, + ...props + }: ButtonHTMLAttributes & { variant?: string }) => ( + + ), cn: (...values: unknown[]) => values.filter(Boolean).join(' '), Input: ({ ref, @@ -239,7 +247,7 @@ function setInputValue(input: HTMLInputElement, value: string) { async function addMessageAndAttachment(message: string, file: File) { const messageInput = container.querySelector( - 'input[placeholder="Type a message..."]' + 'input[placeholder="Send a test message..."]' ) const fileInput = container.querySelector('#floating-chat-file-input') if (!messageInput || !fileInput) throw new Error('Expected chat inputs') @@ -342,7 +350,8 @@ describe('floating chat attachment uploads', () => { ]) expect(container.textContent).not.toContain('diagram.png') expect( - container.querySelector('input[placeholder="Type a message..."]')?.value + container.querySelector('input[placeholder="Send a test message..."]') + ?.value ).toBe('') expect(mockRevokeObjectURL).toHaveBeenCalledWith('blob:diagram-preview') }) @@ -360,7 +369,8 @@ describe('floating chat attachment uploads', () => { expect(mockFileReader).not.toHaveBeenCalled() expect( - container.querySelector('input[placeholder="Type a message..."]')?.value + container.querySelector('input[placeholder="Send a test message..."]') + ?.value ).toBe('Summarize this report') expect(container.querySelector('img[alt="report.png"]')).not.toBeNull() expect(container.textContent).toContain( diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/chat/chat.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/chat/chat.tsx index 51a52dae612..806500d948e 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/chat/chat.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/chat/chat.tsx @@ -4,6 +4,7 @@ import { type KeyboardEvent, useCallback, useEffect, useMemo, useRef, useState } import { Badge, Button, + Chip, cn, Input, Popover, @@ -13,6 +14,7 @@ import { PopoverTrigger, Tooltip, Trash, + thinScrollbarClass, } from '@sim/emcn' import { ArrowUp, CircleAlert, Download, MoreVertical, Paperclip, Square, X } from '@sim/emcn/icons' import { createLogger } from '@sim/logger' @@ -893,7 +895,7 @@ export function Chat() {
- Chat + + Test chat + - {/* Start inputs button and output selector - with max-width to prevent overflow */} + {/* Output selector - with max-width to prevent overflow */}
e.stopPropagation()} > - {shouldShowConfigureStartInputsButton && ( - - )} -
@@ -998,14 +989,38 @@ export function Chat() { {/* Chat content */}
+ {shouldShowConfigureStartInputsButton && ( +
+
+
+ Set up test inputs +
+
+ Add message, conversation, and file fields to the Start block. +
+
+ + Configure + +
+ )} + {/* Messages */}
{workflowMessages.length === 0 ? ( -
- No messages yet +
+
+ Test this workflow +
+
+ Messages run the current draft and may trigger connected actions. +
) : ( -
+
{workflowMessages.map((message) => ( @@ -1030,7 +1045,7 @@ export function Chat() {
-
+
File upload error
@@ -1071,7 +1086,7 @@ export function Chat() { setHistoryIndex(-1) }} onKeyDown={handleKeyPress} - placeholder={isDragOver ? 'Drop files here...' : 'Type a message...'} + placeholder={isDragOver ? 'Drop files here...' : 'Send a test message...'} className='w-full border-0 bg-transparent pr-[56px] pl-1 shadow-none focus-visible:ring-0 focus-visible:ring-offset-0' disabled={!activeWorkflowId} /> diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/deploy/components/deploy-modal/components/api/api.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/deploy/components/deploy-modal/components/api/api.tsx index d06f205010f..18b1095061f 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/deploy/components/deploy-modal/components/api/api.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/deploy/components/deploy-modal/components/api/api.tsx @@ -1,16 +1,7 @@ 'use client' import { useMemo, useState } from 'react' -import { - Button, - ButtonGroup, - ButtonGroupItem, - Code, - Combobox, - Label, - Skeleton, - Tooltip, -} from '@sim/emcn' +import { Chip, ChipDropdown, ChipSwitch, Code, Label, Skeleton, Tooltip } from '@sim/emcn' import { Check, Clipboard } from '@sim/emcn/icons' import { AGENT_STREAM_PROTOCOL_HEADER_LABEL, @@ -55,6 +46,17 @@ const LANGUAGE_LABELS: Record = { typescript: 'TypeScript', } +const LANGUAGE_OPTIONS = (Object.keys(LANGUAGE_LABELS) as CodeLanguage[]).map((value) => ({ + value, + label: LANGUAGE_LABELS[value], +})) + +const ASYNC_EXAMPLE_OPTIONS = [ + { label: 'Execute Job', value: 'execute' }, + { label: 'Check Status', value: 'status' }, + { label: 'Rate Limits', value: 'rate-limits' }, +] as const + const LANGUAGE_SYNTAX: Record = { curl: 'javascript', python: 'python', @@ -416,19 +418,6 @@ console.log(limits);` } } - const getAsyncExampleTitle = () => { - switch (asyncExampleType) { - case 'execute': - return 'Start Execution' - case 'status': - return 'Check Status' - case 'rate-limits': - return 'Usage Limits' - default: - return 'Start Execution' - } - } - const handleCopy = (key: keyof CopiedState, value: string) => { navigator.clipboard.writeText(value) setCopied((prev) => ({ ...prev, [key]: true })) @@ -460,13 +449,13 @@ console.log(limits);`
- setLanguage(val as CodeLanguage)}> - {(Object.keys(LANGUAGE_LABELS) as CodeLanguage[]).map((lang) => ( - - {LANGUAGE_LABELS[lang]} - - ))} - +
@@ -474,14 +463,12 @@ console.log(limits);` - + aria-label={copied.sync ? 'Command copied' : 'Copy command'} + className='-my-1.5' + /> {copied.sync ? 'Copied' : 'Copy'} @@ -504,14 +491,12 @@ console.log(limits);`
- + aria-label={copied.stream ? 'Command copied' : 'Copy command'} + className='-my-1.5' + /> {copied.stream ? 'Copied' : 'Copy'} @@ -524,6 +509,8 @@ console.log(limits);` placeholder='Select outputs' valueMode='label' align='end' + size='md' + className='w-[140px]' />
@@ -544,31 +531,23 @@ console.log(limits);`
- + aria-label={copied.async ? 'Command copied' : 'Copy command'} + className='-my-1.5' + /> {copied.async ? 'Copied' : 'Copy'} - setAsyncExampleType(value as AsyncExampleType)} align='end' - dropdownWidth={160} />
diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/deploy/components/deploy-modal/components/chat/chat.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/deploy/components/deploy-modal/components/chat/chat.tsx index 510e32667d5..ff10c35aa65 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/deploy/components/deploy-modal/components/chat/chat.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/deploy/components/deploy-modal/components/chat/chat.tsx @@ -2,18 +2,14 @@ import { useEffect, useRef, useState } from 'react' import { - ButtonGroup, - ButtonGroupItem, ChipConfirmModal, ChipEmailsInput, ChipInput, - cn, - Input, + ChipSwitch, + ChipTextarea, Label, Loader, Skeleton, - Switch, - Textarea, Tooltip, } from '@sim/emcn' import { Check, TriangleAlert } from '@sim/emcn/icons' @@ -47,6 +43,10 @@ import { const logger = createLogger('ChatDeploy') const IDENTIFIER_PATTERN = /^[a-z0-9-]+$/ +const BOOLEAN_OPTIONS = [ + { value: 'off', label: 'Off' }, + { value: 'on', label: 'On' }, +] as const interface ChatDeployProps { workflowId: string @@ -402,11 +402,13 @@ export function ChatDeploy({ Include thinking
- updateField('includeThinking', checked)} + onChange={(value) => updateField('includeThinking', value === 'on')} aria-label='Include thinking' + size='compact' />
@@ -416,11 +418,13 @@ export function ChatDeploy({ Include tool calls
- updateField('includeToolCalls', checked)} + onChange={(value) => updateField('includeToolCalls', value === 'on')} aria-label='Include tool calls' + size='compact' />
@@ -446,7 +450,7 @@ export function ChatDeploy({ > Welcome message -