Skip to content

Commit 2741fab

Browse files
committed
Plan
1 parent 2a868aa commit 2741fab

File tree

28 files changed

+894
-104
lines changed

28 files changed

+894
-104
lines changed

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/copilot/components/copilot-message/components/thinking-block.tsx

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import { useEffect, useRef, useState } from 'react'
44
import clsx from 'clsx'
55
import { ChevronUp } from 'lucide-react'
6+
import CopilotMarkdownRenderer from './markdown-renderer'
67

78
/**
89
* Max height for thinking content before internal scrolling kicks in
@@ -144,7 +145,7 @@ export function ThinkingBlock({
144145
return next
145146
})
146147
}}
147-
className='mb-1 inline-flex items-center gap-1 text-left font-[470] font-season text-[var(--text-secondary)] text-sm transition-colors hover:text-[var(--text-primary)]'
148+
className='group mb-1 inline-flex items-center gap-1 text-left font-[470] font-season text-[var(--text-secondary)] text-sm transition-colors hover:text-[var(--text-primary)]'
148149
type='button'
149150
>
150151
<span className='relative inline-block'>
@@ -173,8 +174,8 @@ export function ThinkingBlock({
173174
{hasContent && (
174175
<ChevronUp
175176
className={clsx(
176-
'h-3 w-3 transition-transform',
177-
isExpanded ? 'rotate-180' : 'rotate-90'
177+
'h-3 w-3 transition-all group-hover:opacity-100',
178+
isExpanded ? 'rotate-180 opacity-100' : 'rotate-90 opacity-0'
178179
)}
179180
aria-hidden='true'
180181
/>
@@ -188,10 +189,10 @@ export function ThinkingBlock({
188189
isExpanded ? 'mt-1 max-h-[200px] opacity-100' : 'max-h-0 opacity-0'
189190
)}
190191
>
191-
<pre className='whitespace-pre-wrap font-[470] font-season text-[12px] text-[var(--text-tertiary)] leading-[1.15rem]'>
192-
{content}
193-
<span className='ml-1 inline-block h-2 w-1 animate-pulse bg-[var(--text-tertiary)]' />
194-
</pre>
192+
<div className='whitespace-pre-wrap font-[470] font-season text-[12px] text-[var(--text-muted)] leading-none [&_*]:!text-[var(--text-muted)] [&_*]:!text-[12px] [&_*]:!leading-none [&_*]:!m-0 [&_*]:!p-0 [&_*]:!mb-0 [&_*]:!mt-0 [&_p]:!m-0 [&_h1]:!text-[12px] [&_h1]:!font-semibold [&_h2]:!text-[12px] [&_h2]:!font-semibold [&_h3]:!text-[12px] [&_h3]:!font-semibold [&_code]:!text-[11px] [&_ul]:!pl-4 [&_ul]:!my-0 [&_ol]:!pl-4 [&_ol]:!my-0 [&_li]:!my-0 [&_li]:!py-0 [&_br]:!leading-[0.5]'>
193+
<CopilotMarkdownRenderer content={content} />
194+
<span className='ml-1 inline-block h-2 w-1 animate-pulse bg-[var(--text-muted)]' />
195+
</div>
195196
</div>
196197
</div>
197198
)
@@ -204,16 +205,16 @@ export function ThinkingBlock({
204205
onClick={() => {
205206
setIsExpanded((v) => !v)
206207
}}
207-
className='mb-1 inline-flex items-center gap-1 text-left font-[470] font-season text-[var(--text-secondary)] text-sm transition-colors hover:text-[var(--text-primary)]'
208+
className='group mb-1 inline-flex items-center gap-1 text-left font-[470] font-season text-[var(--text-secondary)] text-sm transition-colors hover:text-[var(--text-primary)]'
208209
type='button'
209210
disabled={!hasContent}
210211
>
211212
<span className='text-[var(--text-tertiary)]'>{durationText}</span>
212213
{hasContent && (
213214
<ChevronUp
214215
className={clsx(
215-
'h-3 w-3 transition-transform',
216-
isExpanded ? 'rotate-180' : 'rotate-90'
216+
'h-3 w-3 transition-all group-hover:opacity-100',
217+
isExpanded ? 'rotate-180 opacity-100' : 'rotate-90 opacity-0'
217218
)}
218219
aria-hidden='true'
219220
/>
@@ -227,9 +228,9 @@ export function ThinkingBlock({
227228
isExpanded ? 'mt-1 max-h-[200px] opacity-100' : 'max-h-0 opacity-0'
228229
)}
229230
>
230-
<pre className='whitespace-pre-wrap font-[470] font-season text-[12px] text-[var(--text-tertiary)] leading-[1.15rem]'>
231-
{content}
232-
</pre>
231+
<div className='whitespace-pre-wrap font-[470] font-season text-[12px] text-[var(--text-muted)] leading-none [&_*]:!text-[var(--text-muted)] [&_*]:!text-[12px] [&_*]:!leading-none [&_*]:!m-0 [&_*]:!p-0 [&_*]:!mb-0 [&_*]:!mt-0 [&_p]:!m-0 [&_h1]:!text-[12px] [&_h1]:!font-semibold [&_h2]:!text-[12px] [&_h2]:!font-semibold [&_h3]:!text-[12px] [&_h3]:!font-semibold [&_code]:!text-[11px] [&_ul]:!pl-4 [&_ul]:!my-0 [&_ol]:!pl-4 [&_ol]:!my-0 [&_li]:!my-0 [&_li]:!py-0 [&_br]:!leading-[0.5]'>
232+
<CopilotMarkdownRenderer content={content} />
233+
</div>
233234
</div>
234235
</div>
235236
)

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/copilot/components/tool-call/tool-call.tsx

Lines changed: 105 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,14 @@ import { Button, Code } from '@/components/emcn'
77
import { ClientToolCallState } from '@/lib/copilot/tools/client/base-tool'
88
import { getClientTool } from '@/lib/copilot/tools/client/manager'
99
import { getRegisteredTools } from '@/lib/copilot/tools/client/registry'
10+
// Initialize all tool UI configs
11+
import '@/lib/copilot/tools/client/init-tool-configs'
12+
import {
13+
getToolUIConfig,
14+
isSpecialTool as isSpecialToolFromConfig,
15+
getSubagentLabels as getSubagentLabelsFromConfig,
16+
hasInterrupt as hasInterruptFromConfig,
17+
} from '@/lib/copilot/tools/client/ui-config'
1018
import CopilotMarkdownRenderer from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/copilot/components/copilot-message/components/markdown-renderer'
1119
import { SmoothStreamingText } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/copilot/components/copilot-message/components/smooth-streaming'
1220
import { ThinkingBlock } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/copilot/components/copilot-message/components/thinking-block'
@@ -164,7 +172,7 @@ export function parseSpecialTags(content: string): ParsedTags {
164172

165173
/**
166174
* PlanSteps component renders the workflow plan steps from the plan subagent
167-
* Only renders the title, not the full plan details
175+
* Displays as a to-do list with checkmarks and strikethrough text
168176
*/
169177
function PlanSteps({
170178
steps,
@@ -193,19 +201,35 @@ function PlanSteps({
193201

194202
return (
195203
<div className='mt-2 overflow-hidden rounded-md border border-[var(--border-1)] bg-[var(--surface-1)]'>
196-
<div className='border-[var(--border-1)] border-b bg-[var(--surface-2)] px-2.5 py-2'>
197-
<span className='font-medium font-season text-[12px] text-[var(--text-primary)]'>
198-
Workflow Plan
204+
<div className='flex items-center gap-2 border-[var(--border-1)] border-b bg-[var(--surface-2)] px-2.5 py-2'>
205+
<svg
206+
className='h-3.5 w-3.5 text-[var(--text-tertiary)]'
207+
viewBox='0 0 24 24'
208+
fill='none'
209+
stroke='currentColor'
210+
strokeWidth='2'
211+
strokeLinecap='round'
212+
strokeLinejoin='round'
213+
>
214+
{/* Three horizontal lines with circles at different positions */}
215+
<line x1='4' y1='6' x2='20' y2='6' />
216+
<circle cx='8' cy='6' r='2' fill='currentColor' />
217+
<line x1='4' y1='12' x2='20' y2='12' />
218+
<circle cx='16' cy='12' r='2' fill='currentColor' />
219+
<line x1='4' y1='18' x2='20' y2='18' />
220+
<circle cx='10' cy='18' r='2' fill='currentColor' />
221+
</svg>
222+
<span className='font-medium text-[12px] text-[var(--text-secondary)]'>To-dos</span>
223+
<span className='font-medium text-[12px] text-[var(--text-tertiary)]'>
224+
{sortedSteps.length}
199225
</span>
200226
</div>
201-
<div className='divide-y divide-[var(--border-1)]'>
227+
<div className='flex flex-col gap-2.5 px-2.5 py-2.5'>
202228
{sortedSteps.map(([num, title], index) => {
203229
const isLastStep = index === sortedSteps.length - 1
204230
return (
205-
<div key={num} className='flex items-start gap-2.5 px-2.5 py-2'>
206-
<div className='flex h-5 w-5 flex-shrink-0 items-center justify-center rounded-full bg-[var(--surface-3)] font-medium font-mono text-[11px] text-[var(--text-secondary)]'>
207-
{num}
208-
</div>
231+
<div key={num} className='flex items-start gap-2'>
232+
<div className='mt-0.5 flex h-4 w-4 flex-shrink-0 items-center justify-center rounded-full border border-[var(--border-2)]' />
209233
<div className='min-w-0 flex-1 text-[12px] text-[var(--text-secondary)] leading-5 [&_code]:px-1 [&_code]:py-0.5 [&_code]:text-[11px] [&_p]:m-0 [&_p]:leading-5'>
210234
{streaming && isLastStep ? (
211235
<SmoothStreamingText content={title} isStreaming={true} />
@@ -797,51 +821,23 @@ const SUBAGENT_SCROLL_INTERVAL = 100
797821

798822
/**
799823
* Get the outer collapse header label for completed subagent tools.
800-
* Returns the label to show when subagent is done (e.g., "Planned", "Thought")
824+
* Uses the tool's UI config.
801825
*/
802826
function getSubagentCompletionLabel(toolName: string): string {
803-
switch (toolName) {
804-
case 'plan':
805-
return 'Planned'
806-
default:
807-
return 'Thought'
808-
}
827+
const labels = getSubagentLabelsFromConfig(toolName, false)
828+
return labels?.completed ?? 'Thought'
809829
}
810830

811831
/**
812-
* Get display labels for subagent tools (legacy - used in SubAgentContent)
832+
* Get display labels for subagent tools.
833+
* Uses the tool's UI config.
813834
*/
814835
function getSubagentLabels(toolName: string, isStreaming: boolean): string {
815-
switch (toolName) {
816-
case 'plan':
817-
return isStreaming ? 'Planning' : 'Planned'
818-
case 'edit':
819-
return isStreaming ? 'Editing' : 'Edited'
820-
case 'debug':
821-
return isStreaming ? 'Debugging' : 'Debugged'
822-
case 'test':
823-
return isStreaming ? 'Testing' : 'Tested'
824-
case 'deploy':
825-
return isStreaming ? 'Deploying' : 'Deployed'
826-
case 'evaluate':
827-
return isStreaming ? 'Evaluating' : 'Evaluated'
828-
case 'auth':
829-
return isStreaming ? 'Authenticating' : 'Authenticated'
830-
case 'research':
831-
return isStreaming ? 'Researching' : 'Researched'
832-
case 'knowledge':
833-
return isStreaming ? 'Managing knowledge' : 'Knowledge managed'
834-
case 'custom_tool':
835-
return isStreaming ? 'Managing custom tool' : 'Custom tool managed'
836-
case 'tour':
837-
return isStreaming ? 'Touring' : 'Tour complete'
838-
case 'info':
839-
return isStreaming ? 'Getting info' : 'Info retrieved'
840-
case 'workflow':
841-
return isStreaming ? 'Managing workflow' : 'Workflow managed'
842-
default:
843-
return isStreaming ? 'Processing' : 'Processed'
836+
const labels = getSubagentLabelsFromConfig(toolName, isStreaming)
837+
if (labels) {
838+
return isStreaming ? labels.streaming : labels.completed
844839
}
840+
return isStreaming ? 'Processing' : 'Processed'
845841
}
846842

847843
/**
@@ -915,7 +911,7 @@ function SubAgentContent({
915911
return next
916912
})
917913
}}
918-
className='mb-1 inline-flex items-center gap-1 text-left font-[470] font-season text-[var(--text-secondary)] text-sm transition-colors hover:text-[var(--text-primary)]'
914+
className='group mb-1 inline-flex items-center gap-1 text-left font-[470] font-season text-[var(--text-secondary)] text-sm transition-colors hover:text-[var(--text-primary)]'
919915
type='button'
920916
disabled={!hasContent}
921917
>
@@ -947,8 +943,8 @@ function SubAgentContent({
947943
{hasContent && (
948944
<ChevronUp
949945
className={clsx(
950-
'h-3 w-3 transition-transform',
951-
isExpanded ? 'rotate-180' : 'rotate-90'
946+
'h-3 w-3 transition-all group-hover:opacity-100',
947+
isExpanded ? 'rotate-180 opacity-100' : 'rotate-90 opacity-0'
952948
)}
953949
aria-hidden='true'
954950
/>
@@ -1193,14 +1189,14 @@ function SubagentContentRenderer({
11931189
<div className='w-full'>
11941190
<button
11951191
onClick={() => setIsExpanded((v) => !v)}
1196-
className='mb-1 inline-flex items-center gap-1 text-left font-[470] font-season text-[var(--text-secondary)] text-sm transition-colors hover:text-[var(--text-primary)]'
1192+
className='group mb-1 inline-flex items-center gap-1 text-left font-[470] font-season text-[var(--text-secondary)] text-sm transition-colors hover:text-[var(--text-primary)]'
11971193
type='button'
11981194
>
11991195
<span className='text-[var(--text-tertiary)]'>{durationText}</span>
12001196
<ChevronUp
12011197
className={clsx(
1202-
'h-3 w-3 transition-transform',
1203-
isExpanded ? 'rotate-180' : 'rotate-90'
1198+
'h-3 w-3 transition-all group-hover:opacity-100',
1199+
isExpanded ? 'rotate-180 opacity-100' : 'rotate-90 opacity-0'
12041200
)}
12051201
aria-hidden='true'
12061202
/>
@@ -1223,18 +1219,10 @@ function SubagentContentRenderer({
12231219

12241220
/**
12251221
* Determines if a tool call is "special" and should display with gradient styling.
1226-
* Only workflow operation tools (edit, build, run, deploy) get the purple gradient.
1222+
* Uses the tool's UI config.
12271223
*/
12281224
function isSpecialToolCall(toolCall: CopilotToolCall): boolean {
1229-
const workflowOperationTools = [
1230-
'edit_workflow',
1231-
'build_workflow',
1232-
'run_workflow',
1233-
'deploy_api',
1234-
'deploy_chat',
1235-
]
1236-
1237-
return workflowOperationTools.includes(toolCall.name)
1225+
return isSpecialToolFromConfig(toolCall.name)
12381226
}
12391227

12401228
/**
@@ -1468,29 +1456,27 @@ function WorkflowEditSummary({ toolCall }: { toolCall: CopilotToolCall }) {
14681456
key={`${type}-${change.blockId}`}
14691457
className='overflow-hidden rounded-md border border-[var(--border-1)] bg-[var(--surface-1)]'
14701458
>
1471-
{/* Block header */}
1472-
<div className='flex items-center justify-between px-2.5 py-2'>
1473-
<div className='flex items-center gap-2'>
1474-
{/* Toolbar-style icon: colored square with white icon */}
1475-
<div
1476-
className='flex h-4 w-4 flex-shrink-0 items-center justify-center rounded-[3px]'
1477-
style={{ background: bgColor }}
1478-
>
1479-
{Icon && <Icon className='h-[10px] w-[10px] text-white' />}
1480-
</div>
1481-
<span
1482-
className={`font-medium font-season text-[12px] ${type === 'delete' ? 'text-[var(--text-tertiary)] line-through' : 'text-[var(--text-primary)]'}`}
1483-
>
1484-
{change.blockName}
1485-
</span>
1459+
{/* Block header - gray background like plan/table headers */}
1460+
<div className='flex items-center bg-[var(--surface-2)] px-2.5 py-2'>
1461+
{/* Toolbar-style icon: colored square with white icon */}
1462+
<div
1463+
className='flex h-4 w-4 flex-shrink-0 items-center justify-center rounded-[3px]'
1464+
style={{ background: bgColor }}
1465+
>
1466+
{Icon && <Icon className='h-[10px] w-[10px] text-white' />}
14861467
</div>
1487-
{/* Action icon in top right */}
1488-
<span className={`font-bold font-mono text-[14px] ${color}`}>{symbol}</span>
1468+
<span
1469+
className={`ml-2 font-medium font-season text-[12px] ${type === 'delete' ? 'text-[var(--text-tertiary)] line-through' : 'text-[var(--text-primary)]'}`}
1470+
>
1471+
{change.blockName}
1472+
</span>
1473+
{/* Action icon next to block name */}
1474+
<span className={`ml-1.5 font-bold font-mono text-[12px] ${color}`}>{symbol}</span>
14891475
</div>
14901476

1491-
{/* Subblock details - uses same title and value formatting as canvas */}
1477+
{/* Subblock details - dark background like table/plan body */}
14921478
{subBlocksToShow && subBlocksToShow.length > 0 && (
1493-
<div className='border-[var(--border-1)] border-t bg-[var(--surface-2)] px-2.5 py-1.5'>
1479+
<div className='border-[var(--border-1)] border-t px-2.5 py-1.5'>
14941480
{subBlocksToShow.map((sb) => {
14951481
// Mask password fields like the canvas does
14961482
const displayValue = sb.isPassword ? '•••' : getDisplayValue(sb.value)
@@ -1533,6 +1519,12 @@ function isIntegrationTool(toolName: string): boolean {
15331519
}
15341520

15351521
function shouldShowRunSkipButtons(toolCall: CopilotToolCall): boolean {
1522+
// First check UI config for interrupt
1523+
if (hasInterruptFromConfig(toolCall.name) && toolCall.state === 'pending') {
1524+
return true
1525+
}
1526+
1527+
// Then check instance-level interrupt
15361528
const instance = getClientTool(toolCall.id)
15371529
let hasInterrupt = !!instance?.getInterruptDisplays?.()
15381530
if (!hasInterrupt) {
@@ -1895,21 +1887,39 @@ export function ToolCall({ toolCall: toolCallProp, toolCallId, onStateChange }:
18951887
if (!isClientTool && !isIntegrationToolInBuildMode) {
18961888
return null
18971889
}
1890+
// Check if tool has params table config (meaning it's expandable)
1891+
const hasParamsTable = !!getToolUIConfig(toolCall.name)?.paramsTable
18981892
const isExpandableTool =
1893+
hasParamsTable ||
18991894
toolCall.name === 'make_api_request' ||
19001895
toolCall.name === 'set_global_workflow_variables' ||
19011896
toolCall.name === 'run_workflow'
19021897

19031898
const showButtons = shouldShowRunSkipButtons(toolCall)
1899+
1900+
// Check UI config for secondary action
1901+
const toolUIConfig = getToolUIConfig(toolCall.name)
1902+
const secondaryAction = toolUIConfig?.secondaryAction
1903+
const showSecondaryAction =
1904+
secondaryAction &&
1905+
secondaryAction.showInStates.includes(toolCall.state as ClientToolCallState)
1906+
1907+
// Legacy fallbacks for tools that haven't migrated to UI config
19041908
const showMoveToBackground =
1905-
toolCall.name === 'run_workflow' &&
1906-
(toolCall.state === (ClientToolCallState.executing as any) ||
1907-
toolCall.state === ('executing' as any))
1909+
showSecondaryAction && secondaryAction?.text === 'Move to Background'
1910+
? true
1911+
: !secondaryAction &&
1912+
toolCall.name === 'run_workflow' &&
1913+
(toolCall.state === (ClientToolCallState.executing as any) ||
1914+
toolCall.state === ('executing' as any))
19081915

19091916
const showWake =
1910-
toolCall.name === 'sleep' &&
1911-
(toolCall.state === (ClientToolCallState.executing as any) ||
1912-
toolCall.state === ('executing' as any))
1917+
showSecondaryAction && secondaryAction?.text === 'Wake'
1918+
? true
1919+
: !secondaryAction &&
1920+
toolCall.name === 'sleep' &&
1921+
(toolCall.state === (ClientToolCallState.executing as any) ||
1922+
toolCall.state === ('executing' as any))
19131923

19141924
const handleStateChange = (state: any) => {
19151925
forceUpdate({})
@@ -2262,8 +2272,12 @@ export function ToolCall({ toolCall: toolCallProp, toolCallId, onStateChange }:
22622272
return null
22632273
}
22642274

2265-
// Special handling for set_environment_variables - always stacked, always expanded
2266-
if (toolCall.name === 'set_environment_variables' && toolCall.state === 'pending') {
2275+
// Special handling for tools with alwaysExpanded config (e.g., set_environment_variables)
2276+
const isAlwaysExpanded = toolUIConfig?.alwaysExpanded
2277+
if (
2278+
(isAlwaysExpanded || toolCall.name === 'set_environment_variables') &&
2279+
toolCall.state === 'pending'
2280+
) {
22672281
const isEnvVarsClickable = isAutoAllowed
22682282

22692283
const handleEnvVarsClick = () => {
@@ -2313,8 +2327,8 @@ export function ToolCall({ toolCall: toolCallProp, toolCallId, onStateChange }:
23132327
)
23142328
}
23152329

2316-
// Special rendering for function_execute - show code block
2317-
if (toolCall.name === 'function_execute') {
2330+
// Special rendering for tools with 'code' customRenderer (e.g., function_execute)
2331+
if (toolUIConfig?.customRenderer === 'code' || toolCall.name === 'function_execute') {
23182332
const code = params.code || ''
23192333
const isFunctionExecuteClickable = isAutoAllowed
23202334

0 commit comments

Comments
 (0)