Skip to content

Commit 6750024

Browse files
committed
Diff view in chat
1 parent 9d23a3f commit 6750024

File tree

2 files changed

+157
-69
lines changed
  • apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/copilot/components

2 files changed

+157
-69
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { ChevronUp } from 'lucide-react'
77
/**
88
* Max height for thinking content before internal scrolling kicks in
99
*/
10-
const THINKING_MAX_HEIGHT = 125
10+
const THINKING_MAX_HEIGHT = 200
1111

1212
/**
1313
* Interval for auto-scroll during streaming (ms)

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

Lines changed: 156 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -385,7 +385,7 @@ function SubAgentToolCall({ toolCall: toolCallProp }: { toolCall: CopilotToolCal
385385
/>
386386
)}
387387
{renderSubAgentTable()}
388-
<WorkflowEditSummary toolCall={toolCall} />
388+
{/* WorkflowEditSummary is rendered outside SubAgentContent for edit subagent */}
389389
{showButtons && <RunSkipButtons toolCall={toolCall} />}
390390
</div>
391391
)
@@ -406,7 +406,7 @@ function getDisplayNameForSubAgent(toolCall: CopilotToolCall): string {
406406
/**
407407
* Max height for subagent content before internal scrolling kicks in
408408
*/
409-
const SUBAGENT_MAX_HEIGHT = 125
409+
const SUBAGENT_MAX_HEIGHT = 200
410410

411411
/**
412412
* Interval for auto-scroll during streaming (ms)
@@ -582,6 +582,16 @@ function SubAgentContent({
582582
})}
583583
</div>
584584
)}
585+
586+
{/* Render WorkflowEditSummary outside the collapsible container for edit_workflow tool calls */}
587+
{blocks
588+
.filter((block) => block.type === 'subagent_tool_call' && block.toolCall?.name === 'edit_workflow')
589+
.map((block, index) => (
590+
<WorkflowEditSummary
591+
key={`edit-summary-${block.toolCall?.id || index}`}
592+
toolCall={block.toolCall!}
593+
/>
594+
))}
585595
</div>
586596
)
587597
}
@@ -607,16 +617,25 @@ function isSpecialToolCall(toolCall: CopilotToolCall): boolean {
607617
* Expands inline on click to show individual blocks with their icons.
608618
*/
609619
function WorkflowEditSummary({ toolCall }: { toolCall: CopilotToolCall }) {
610-
const [isExpanded, setIsExpanded] = useState(false)
611-
612-
// Get workflow name from registry
613-
const activeWorkflowId = useWorkflowRegistry((s) => s.activeWorkflowId)
614-
const workflows = useWorkflowRegistry((s) => s.workflows)
615-
const workflowName = activeWorkflowId ? workflows[activeWorkflowId]?.name : undefined
616-
617620
// Get block data from current workflow state
618621
const blocks = useWorkflowStore((s) => s.blocks)
619622

623+
// Cache block info on first render (before diff is applied) so we can show
624+
// deleted blocks properly even after they're removed from the workflow
625+
const cachedBlockInfoRef = useRef<Record<string, { name: string; type: string }>>({})
626+
627+
// Update cache with current block info (only add, never remove)
628+
useEffect(() => {
629+
for (const [blockId, block] of Object.entries(blocks)) {
630+
if (!cachedBlockInfoRef.current[blockId]) {
631+
cachedBlockInfoRef.current[blockId] = {
632+
name: block.name || '',
633+
type: block.type || '',
634+
}
635+
}
636+
}
637+
}, [blocks])
638+
620639
// Show for edit_workflow regardless of state
621640
if (toolCall.name !== 'edit_workflow') {
622641
return null
@@ -632,10 +651,19 @@ function WorkflowEditSummary({ toolCall }: { toolCall: CopilotToolCall }) {
632651
}
633652

634653
// Group operations by type with block info
654+
interface SubBlockPreview {
655+
id: string
656+
value: any
657+
}
658+
635659
interface BlockChange {
636660
blockId: string
637661
blockName: string
638662
blockType: string
663+
/** All subblocks for add operations */
664+
subBlocks?: SubBlockPreview[]
665+
/** Only changed subblocks for edit operations */
666+
changedSubBlocks?: SubBlockPreview[]
639667
}
640668

641669
const addedBlocks: BlockChange[] = []
@@ -646,10 +674,11 @@ function WorkflowEditSummary({ toolCall }: { toolCall: CopilotToolCall }) {
646674
const blockId = op.block_id
647675
if (!blockId) continue
648676

649-
// Get block info from current workflow state or operation params
677+
// Get block info from current workflow state, cached state, or operation params
650678
const currentBlock = blocks[blockId]
651-
let blockName = currentBlock?.name || ''
652-
let blockType = currentBlock?.type || ''
679+
const cachedBlock = cachedBlockInfoRef.current[blockId]
680+
let blockName = currentBlock?.name || cachedBlock?.name || ''
681+
let blockType = currentBlock?.type || cachedBlock?.type || ''
653682

654683
// For add operations, get info from params (type is stored as params.type)
655684
if (op.operation_type === 'add' && op.params) {
@@ -662,11 +691,45 @@ function WorkflowEditSummary({ toolCall }: { toolCall: CopilotToolCall }) {
662691
blockType = op.params.type || ''
663692
}
664693

694+
// Skip edge-only edit operations (like how we don't highlight blocks on canvas for edge changes)
695+
// An edit is edge-only if params only contains 'connections' and nothing else meaningful
696+
if (op.operation_type === 'edit' && op.params) {
697+
const paramKeys = Object.keys(op.params)
698+
const isEdgeOnlyEdit = paramKeys.length === 1 && paramKeys[0] === 'connections'
699+
if (isEdgeOnlyEdit) {
700+
continue
701+
}
702+
}
703+
704+
// For delete operations, check if block info was provided in operation
705+
if (op.operation_type === 'delete') {
706+
// Some delete operations may include block_name and block_type
707+
blockName = blockName || op.block_name || ''
708+
blockType = blockType || op.block_type || ''
709+
}
710+
665711
// Fallback name to type or ID
666712
if (!blockName) blockName = blockType || blockId
667713

668714
const change: BlockChange = { blockId, blockName, blockType }
669715

716+
// Extract subblock info from operation params
717+
if (op.params?.inputs && typeof op.params.inputs === 'object') {
718+
const subBlocks: SubBlockPreview[] = []
719+
for (const [id, value] of Object.entries(op.params.inputs)) {
720+
// Skip empty values and connections
721+
if (value === null || value === undefined || value === '') continue
722+
subBlocks.push({ id, value })
723+
}
724+
if (subBlocks.length > 0) {
725+
if (op.operation_type === 'add') {
726+
change.subBlocks = subBlocks
727+
} else if (op.operation_type === 'edit') {
728+
change.changedSubBlocks = subBlocks
729+
}
730+
}
731+
}
732+
670733
switch (op.operation_type) {
671734
case 'add':
672735
addedBlocks.push(change)
@@ -691,83 +754,108 @@ function WorkflowEditSummary({ toolCall }: { toolCall: CopilotToolCall }) {
691754
return getBlock(blockType)
692755
}
693756

694-
// Render a single block row (toolbar style: colored square with white icon)
695-
const renderBlockRow = (
757+
// Format subblock value for display
758+
const formatSubBlockValue = (value: any): string => {
759+
if (value === null || value === undefined) return ''
760+
if (typeof value === 'string') {
761+
// Truncate long strings
762+
return value.length > 60 ? `${value.slice(0, 60)}...` : value
763+
}
764+
if (typeof value === 'boolean') return value ? 'true' : 'false'
765+
if (typeof value === 'number') return String(value)
766+
if (Array.isArray(value)) {
767+
if (value.length === 0) return '[]'
768+
return `[${value.length} items]`
769+
}
770+
if (typeof value === 'object') {
771+
const keys = Object.keys(value)
772+
if (keys.length === 0) return '{}'
773+
return `{${keys.length} fields}`
774+
}
775+
return String(value)
776+
}
777+
778+
// Format subblock ID to readable label
779+
const formatSubBlockLabel = (id: string): string => {
780+
return id
781+
.replace(/([A-Z])/g, ' $1')
782+
.replace(/[_-]/g, ' ')
783+
.trim()
784+
.split(' ')
785+
.map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
786+
.join(' ')
787+
}
788+
789+
// Render a single block item with action icon and details
790+
const renderBlockItem = (
696791
change: BlockChange,
697792
type: 'add' | 'edit' | 'delete'
698793
) => {
699794
const blockConfig = getBlockConfig(change.blockType)
700795
const Icon = blockConfig?.icon
701796
const bgColor = blockConfig?.bgColor || '#6B7280'
702797

703-
const symbols = {
798+
const actionIcons = {
704799
add: { symbol: '+', color: 'text-[#22c55e]' },
705-
edit: { symbol: '', color: 'text-[#f97316]' },
800+
edit: { symbol: '~', color: 'text-[#f97316]' },
706801
delete: { symbol: '-', color: 'text-[#ef4444]' },
707802
}
708-
const { symbol, color } = symbols[type]
803+
const { symbol, color } = actionIcons[type]
804+
805+
const subBlocksToShow = type === 'add' ? change.subBlocks : type === 'edit' ? change.changedSubBlocks : undefined
709806

710807
return (
711808
<div
712809
key={`${type}-${change.blockId}`}
713-
className='flex items-center gap-2 px-2.5 py-1.5'
810+
className='rounded-md border border-[var(--border-1)] bg-[var(--surface-1)] overflow-hidden'
714811
>
715-
<span className={`font-mono text-[11px] font-medium ${color} w-3`}>{symbol}</span>
716-
{/* Toolbar-style icon: colored square with white icon */}
717-
<div
718-
className='flex h-4 w-4 flex-shrink-0 items-center justify-center rounded-[3px]'
719-
style={{ background: bgColor }}
720-
>
721-
{Icon && <Icon className='h-[10px] w-[10px] text-white' />}
812+
{/* Block header */}
813+
<div className='flex items-center justify-between px-2.5 py-2'>
814+
<div className='flex items-center gap-2'>
815+
{/* Toolbar-style icon: colored square with white icon */}
816+
<div
817+
className='flex h-4 w-4 flex-shrink-0 items-center justify-center rounded-[3px]'
818+
style={{ background: bgColor }}
819+
>
820+
{Icon && <Icon className='h-[10px] w-[10px] text-white' />}
821+
</div>
822+
<span
823+
className={`font-medium font-season text-[12px] ${type === 'delete' ? 'text-[var(--text-tertiary)] line-through' : 'text-[var(--text-primary)]'}`}
824+
>
825+
{change.blockName}
826+
</span>
827+
</div>
828+
{/* Action icon in top right */}
829+
<span className={`font-mono text-[14px] font-bold ${color}`}>{symbol}</span>
722830
</div>
723-
<span
724-
className={`font-season text-[12px] ${type === 'delete' ? 'text-[var(--text-tertiary)] line-through' : 'text-[var(--text-secondary)]'}`}
725-
>
726-
{change.blockName}
727-
</span>
831+
832+
{/* Subblock details */}
833+
{subBlocksToShow && subBlocksToShow.length > 0 && (
834+
<div className='border-t border-[var(--border-1)] bg-[var(--surface-2)] px-2.5 py-1.5'>
835+
{subBlocksToShow.map((sb) => (
836+
<div
837+
key={sb.id}
838+
className='flex items-start gap-1.5 py-0.5 text-[11px]'
839+
>
840+
<span className={`font-medium ${type === 'edit' ? 'text-[#f97316]' : 'text-[var(--text-tertiary)]'}`}>
841+
{formatSubBlockLabel(sb.id)}:
842+
</span>
843+
<span className='text-[var(--text-muted)] line-clamp-1 break-all'>
844+
{formatSubBlockValue(sb.value)}
845+
</span>
846+
</div>
847+
))}
848+
</div>
849+
)}
728850
</div>
729851
)
730852
}
731853

732854
return (
733-
<div className='mt-2 w-full overflow-hidden rounded-md border border-[var(--border-1)] bg-[var(--surface-1)]'>
734-
{/* Header row - always visible */}
735-
<button
736-
type='button'
737-
onClick={() => setIsExpanded(!isExpanded)}
738-
className='flex w-full items-center justify-between px-2.5 py-2 transition-colors hover:bg-[var(--surface-2)]'
739-
>
740-
<div className='flex items-center gap-2'>
741-
<span className='font-medium font-season text-[12px] text-[var(--text-primary)]'>
742-
{workflowName || 'Workflow'}
743-
</span>
744-
<span className='flex items-center gap-1.5'>
745-
{addedBlocks.length > 0 && (
746-
<span className='font-mono text-[11px] font-medium text-[#22c55e]'>+{addedBlocks.length}</span>
747-
)}
748-
{editedBlocks.length > 0 && (
749-
<span className='font-mono text-[11px] font-medium text-[#f97316]'>{editedBlocks.length}</span>
750-
)}
751-
{deletedBlocks.length > 0 && (
752-
<span className='font-mono text-[11px] font-medium text-[#ef4444]'>-{deletedBlocks.length}</span>
753-
)}
754-
</span>
755-
</div>
756-
{isExpanded ? (
757-
<ChevronUp className='h-3.5 w-3.5 text-[var(--text-tertiary)]' />
758-
) : (
759-
<ChevronDown className='h-3.5 w-3.5 text-[var(--text-tertiary)]' />
760-
)}
761-
</button>
762-
763-
{/* Expanded block list */}
764-
{isExpanded && (
765-
<div className='border-t border-[var(--border-1)]'>
766-
{addedBlocks.map((change) => renderBlockRow(change, 'add'))}
767-
{editedBlocks.map((change) => renderBlockRow(change, 'edit'))}
768-
{deletedBlocks.map((change) => renderBlockRow(change, 'delete'))}
769-
</div>
770-
)}
855+
<div className='mt-2 flex flex-col gap-1.5'>
856+
{addedBlocks.map((change) => renderBlockItem(change, 'add'))}
857+
{editedBlocks.map((change) => renderBlockItem(change, 'edit'))}
858+
{deletedBlocks.map((change) => renderBlockItem(change, 'delete'))}
771859
</div>
772860
)
773861
}

0 commit comments

Comments
 (0)