Skip to content

Commit 077a902

Browse files
committed
feat(copilot): editable input component
1 parent fd2c4b6 commit 077a902

File tree

1 file changed

+187
-90
lines changed
  • apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/copilot/components/tool-call

1 file changed

+187
-90
lines changed

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

Lines changed: 187 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33
import { useEffect, useMemo, useRef, useState } from 'react'
44
import clsx from 'clsx'
55
import { ChevronUp, LayoutList } from 'lucide-react'
6-
import { Button, Code } from '@/components/emcn'
6+
import Editor from 'react-simple-code-editor'
7+
import { Button, Code, getCodeEditorProps, highlight, languages } from '@/components/emcn'
78
import { ClientToolCallState } from '@/lib/copilot/tools/client/base-tool'
89
import { getClientTool } from '@/lib/copilot/tools/client/manager'
910
import { getRegisteredTools } from '@/lib/copilot/tools/client/registry'
@@ -753,36 +754,70 @@ function SubAgentToolCall({ toolCall: toolCallProp }: { toolCall: CopilotToolCal
753754
const safeInputs = inputs && typeof inputs === 'object' ? inputs : {}
754755
const inputEntries = Object.entries(safeInputs)
755756
if (inputEntries.length === 0) return null
757+
758+
/**
759+
* Format a value for display - handles objects, arrays, and primitives
760+
*/
761+
const formatValue = (value: unknown): string => {
762+
if (value === null || value === undefined) return '-'
763+
if (typeof value === 'string') return value || '-'
764+
if (typeof value === 'number' || typeof value === 'boolean') return String(value)
765+
try {
766+
return JSON.stringify(value, null, 2)
767+
} catch {
768+
return String(value)
769+
}
770+
}
771+
772+
/**
773+
* Check if a value is a complex type (object or array)
774+
*/
775+
const isComplex = (value: unknown): boolean => {
776+
return typeof value === 'object' && value !== null
777+
}
778+
756779
return (
757-
<div className='mt-1.5 w-full overflow-hidden rounded-[4px] border border-[var(--border-1)] bg-[var(--surface-1)]'>
758-
<table className='w-full table-fixed bg-transparent'>
759-
<thead className='bg-transparent'>
760-
<tr className='border-[var(--border-1)] border-b bg-transparent'>
761-
<th className='w-[36%] border-[var(--border-1)] border-r bg-transparent px-[10px] py-[5px] text-left font-medium text-[12px] text-[var(--text-tertiary)]'>
762-
Input
763-
</th>
764-
<th className='w-[64%] bg-transparent px-[10px] py-[5px] text-left font-medium text-[12px] text-[var(--text-tertiary)]'>
765-
Value
766-
</th>
767-
</tr>
768-
</thead>
769-
<tbody className='bg-transparent'>
770-
{inputEntries.map(([key, value]) => (
771-
<tr key={key} className='border-[var(--border-1)] border-t bg-transparent'>
772-
<td className='w-[36%] border-[var(--border-1)] border-r bg-transparent px-[10px] py-[6px]'>
773-
<span className='truncate font-medium text-[var(--text-primary)] text-xs'>
774-
{key}
775-
</span>
776-
</td>
777-
<td className='w-[64%] bg-transparent px-[10px] py-[6px]'>
778-
<span className='font-mono text-[var(--text-muted)] text-xs'>
779-
{String(value)}
780+
<div className='mt-1.5 w-full overflow-hidden rounded-md border border-[var(--border-1)] bg-[var(--surface-1)]'>
781+
{/* Header */}
782+
<div className='flex items-center gap-[8px] border-[var(--border-1)] border-b bg-[var(--surface-2)] p-[8px]'>
783+
<span className='font-medium text-[12px] text-[var(--text-primary)]'>Input</span>
784+
<span className='flex-shrink-0 font-medium text-[12px] text-[var(--text-tertiary)]'>
785+
{inputEntries.length}
786+
</span>
787+
</div>
788+
{/* Input entries */}
789+
<div className='flex flex-col'>
790+
{inputEntries.map(([key, value], index) => {
791+
const formattedValue = formatValue(value)
792+
const needsCodeViewer = isComplex(value)
793+
794+
return (
795+
<div
796+
key={key}
797+
className={clsx(
798+
'flex flex-col gap-1 px-[10px] py-[6px]',
799+
index > 0 && 'border-[var(--border-1)] border-t'
800+
)}
801+
>
802+
{/* Input key */}
803+
<span className='font-medium text-[11px] text-[var(--text-primary)]'>{key}</span>
804+
{/* Value display */}
805+
{needsCodeViewer ? (
806+
<Code.Viewer
807+
code={formattedValue}
808+
language='json'
809+
showGutter={false}
810+
className='max-h-[80px] min-h-0'
811+
/>
812+
) : (
813+
<span className='font-mono text-[11px] text-[var(--text-muted)] leading-[1.3]'>
814+
{formattedValue}
780815
</span>
781-
</td>
782-
</tr>
783-
))}
784-
</tbody>
785-
</table>
816+
)}
817+
</div>
818+
)
819+
})}
820+
</div>
786821
</div>
787822
)
788823
}
@@ -2292,74 +2327,136 @@ export function ToolCall({ toolCall: toolCallProp, toolCallId, onStateChange }:
22922327
const safeInputs = inputs && typeof inputs === 'object' ? inputs : {}
22932328
const inputEntries = Object.entries(safeInputs)
22942329

2295-
// Don't show the table if there are no inputs
2330+
// Don't show the section if there are no inputs
22962331
if (inputEntries.length === 0) {
22972332
return null
22982333
}
22992334

2335+
/**
2336+
* Format a value for display - handles objects, arrays, and primitives
2337+
*/
2338+
const formatValueForDisplay = (value: unknown): string => {
2339+
if (value === null || value === undefined) return ''
2340+
if (typeof value === 'string') return value
2341+
if (typeof value === 'number' || typeof value === 'boolean') return String(value)
2342+
// For objects and arrays, use JSON.stringify with formatting
2343+
try {
2344+
return JSON.stringify(value, null, 2)
2345+
} catch {
2346+
return String(value)
2347+
}
2348+
}
2349+
2350+
/**
2351+
* Parse a string value back to its original type if possible
2352+
*/
2353+
const parseInputValue = (value: string, originalValue: unknown): unknown => {
2354+
// If original was a primitive, keep as string
2355+
if (typeof originalValue !== 'object' || originalValue === null) {
2356+
return value
2357+
}
2358+
// Try to parse as JSON for objects/arrays
2359+
try {
2360+
return JSON.parse(value)
2361+
} catch {
2362+
return value
2363+
}
2364+
}
2365+
2366+
/**
2367+
* Check if a value is a complex type (object or array)
2368+
*/
2369+
const isComplexValue = (value: unknown): boolean => {
2370+
return typeof value === 'object' && value !== null
2371+
}
2372+
23002373
return (
2301-
<div className='w-full overflow-hidden rounded-[4px] border border-[var(--border-1)] bg-[var(--surface-1)]'>
2302-
<table className='w-full table-fixed bg-transparent'>
2303-
<thead className='bg-transparent'>
2304-
<tr className='border-[var(--border-1)] border-b bg-transparent'>
2305-
<th className='w-[36%] border-[var(--border-1)] border-r bg-transparent px-[10px] py-[5px] text-left font-medium text-[14px] text-[var(--text-tertiary)]'>
2306-
Input
2307-
</th>
2308-
<th className='w-[64%] bg-transparent px-[10px] py-[5px] text-left font-medium text-[14px] text-[var(--text-tertiary)]'>
2309-
Value
2310-
</th>
2311-
</tr>
2312-
</thead>
2313-
<tbody className='bg-transparent'>
2314-
{inputEntries.map(([key, value]) => (
2315-
<tr
2374+
<div className='w-full overflow-hidden rounded-md border border-[var(--border-1)] bg-[var(--surface-1)]'>
2375+
{/* Header */}
2376+
<div className='flex items-center gap-[8px] border-[var(--border-1)] border-b bg-[var(--surface-2)] p-[8px]'>
2377+
<span className='font-medium text-[12px] text-[var(--text-primary)]'>Edit Input</span>
2378+
<span className='flex-shrink-0 font-medium text-[12px] text-[var(--text-tertiary)]'>
2379+
{inputEntries.length}
2380+
</span>
2381+
</div>
2382+
{/* Input entries */}
2383+
<div className='flex flex-col'>
2384+
{inputEntries.map(([key, value], index) => {
2385+
const isComplex = isComplexValue(value)
2386+
const displayValue = formatValueForDisplay(value)
2387+
2388+
return (
2389+
<div
23162390
key={key}
2317-
className='group relative border-[var(--border-1)] border-t bg-transparent'
2391+
className={clsx(
2392+
'flex flex-col gap-1.5 px-[10px] py-[8px]',
2393+
index > 0 && 'border-[var(--border-1)] border-t'
2394+
)}
23182395
>
2319-
<td className='relative w-[36%] border-[var(--border-1)] border-r bg-transparent p-0'>
2320-
<div className='px-[10px] py-[8px]'>
2321-
<span className='truncate font-medium text-[var(--text-primary)] text-xs'>
2322-
{key}
2323-
</span>
2324-
</div>
2325-
</td>
2326-
<td className='relative w-[64%] bg-transparent p-0'>
2327-
<div className='min-w-0 px-[10px] py-[8px]'>
2328-
<input
2329-
type='text'
2330-
value={String(value)}
2331-
onChange={(e) => {
2332-
const newInputs = { ...safeInputs, [key]: e.target.value }
2333-
2334-
// Determine how to update based on original structure
2335-
if (isNestedInWorkflowInput) {
2336-
// Update workflow_input
2337-
setEditedParams({ ...editedParams, workflow_input: newInputs })
2338-
} else if (typeof editedParams.input === 'string') {
2339-
// Input was a JSON string, serialize back
2340-
setEditedParams({ ...editedParams, input: JSON.stringify(newInputs) })
2341-
} else if (editedParams.input && typeof editedParams.input === 'object') {
2342-
// Input is an object
2343-
setEditedParams({ ...editedParams, input: newInputs })
2344-
} else if (
2345-
editedParams.inputs &&
2346-
typeof editedParams.inputs === 'object'
2347-
) {
2348-
// Inputs is an object
2349-
setEditedParams({ ...editedParams, inputs: newInputs })
2350-
} else {
2351-
// Flat structure - update at base level
2352-
setEditedParams({ ...editedParams, [key]: e.target.value })
2353-
}
2354-
}}
2355-
className='w-full bg-transparent font-mono text-[var(--text-muted)] text-xs outline-none focus:text-[var(--text-primary)]'
2356-
/>
2357-
</div>
2358-
</td>
2359-
</tr>
2360-
))}
2361-
</tbody>
2362-
</table>
2396+
{/* Input key */}
2397+
<span className='font-medium text-[11px] text-[var(--text-primary)]'>{key}</span>
2398+
{/* Value editor */}
2399+
{isComplex ? (
2400+
<Code.Container className='max-h-[168px] min-h-[60px]'>
2401+
<Code.Content>
2402+
<Editor
2403+
value={displayValue}
2404+
onValueChange={(newCode) => {
2405+
const parsedValue = parseInputValue(newCode, value)
2406+
const newInputs = { ...safeInputs, [key]: parsedValue }
2407+
2408+
if (isNestedInWorkflowInput) {
2409+
setEditedParams({ ...editedParams, workflow_input: newInputs })
2410+
} else if (typeof editedParams.input === 'string') {
2411+
setEditedParams({ ...editedParams, input: JSON.stringify(newInputs) })
2412+
} else if (
2413+
editedParams.input &&
2414+
typeof editedParams.input === 'object'
2415+
) {
2416+
setEditedParams({ ...editedParams, input: newInputs })
2417+
} else if (
2418+
editedParams.inputs &&
2419+
typeof editedParams.inputs === 'object'
2420+
) {
2421+
setEditedParams({ ...editedParams, inputs: newInputs })
2422+
} else {
2423+
setEditedParams({ ...editedParams, [key]: parsedValue })
2424+
}
2425+
}}
2426+
highlight={(code) => highlight(code, languages.json, 'json')}
2427+
{...getCodeEditorProps()}
2428+
className={clsx(getCodeEditorProps().className, 'min-h-[40px]')}
2429+
style={{ minHeight: '40px' }}
2430+
/>
2431+
</Code.Content>
2432+
</Code.Container>
2433+
) : (
2434+
<input
2435+
type='text'
2436+
value={displayValue}
2437+
onChange={(e) => {
2438+
const parsedValue = parseInputValue(e.target.value, value)
2439+
const newInputs = { ...safeInputs, [key]: parsedValue }
2440+
2441+
if (isNestedInWorkflowInput) {
2442+
setEditedParams({ ...editedParams, workflow_input: newInputs })
2443+
} else if (typeof editedParams.input === 'string') {
2444+
setEditedParams({ ...editedParams, input: JSON.stringify(newInputs) })
2445+
} else if (editedParams.input && typeof editedParams.input === 'object') {
2446+
setEditedParams({ ...editedParams, input: newInputs })
2447+
} else if (editedParams.inputs && typeof editedParams.inputs === 'object') {
2448+
setEditedParams({ ...editedParams, inputs: newInputs })
2449+
} else {
2450+
setEditedParams({ ...editedParams, [key]: parsedValue })
2451+
}
2452+
}}
2453+
className='w-full rounded-[4px] border border-[var(--border-1)] bg-[var(--surface-1)] px-[8px] py-[6px] font-medium font-mono text-[13px] text-[var(--text-primary)] outline-none transition-colors placeholder:text-[var(--text-muted)] focus:outline-none'
2454+
/>
2455+
)}
2456+
</div>
2457+
)
2458+
})}
2459+
</div>
23632460
</div>
23642461
)
23652462
}

0 commit comments

Comments
 (0)