Skip to content

Commit d7fd4a9

Browse files
authored
feat(copilot): diff improvements (#1002)
* Fix abort * Cred updates * Updates * Fix sheet id showing up in diff view * Update diff view * Text overflow * Optimistic accept * Serialization catching * Depth 0 fix * Fix icons * Updates * Lint
1 parent d972bab commit d7fd4a9

File tree

14 files changed

+339
-144
lines changed

14 files changed

+339
-144
lines changed

apps/sim/app/api/copilot/api-keys/validate/route.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ export async function POST(req: NextRequest) {
6565

6666
if (!Number.isNaN(limit) && limit > 0 && currentUsage >= limit) {
6767
// Usage exceeded
68+
logger.info('[API VALIDATION] Usage exceeded', { userId, currentUsage, limit })
6869
return new NextResponse(null, { status: 402 })
6970
}
7071
}

apps/sim/app/api/copilot/chat/route.ts

Lines changed: 37 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -371,26 +371,50 @@ export async function POST(req: NextRequest) {
371371
(currentChat?.conversationId as string | undefined) || conversationId
372372

373373
// If we have a conversationId, only send the most recent user message; else send full history
374-
const messagesForAgent = effectiveConversationId ? [messages[messages.length - 1]] : messages
374+
const latestUserMessage =
375+
[...messages].reverse().find((m) => m?.role === 'user') || messages[messages.length - 1]
376+
const messagesForAgent = effectiveConversationId ? [latestUserMessage] : messages
377+
378+
const requestPayload = {
379+
messages: messagesForAgent,
380+
workflowId,
381+
userId: authenticatedUserId,
382+
stream: stream,
383+
streamToolCalls: true,
384+
mode: mode,
385+
provider: providerToUse,
386+
...(effectiveConversationId ? { conversationId: effectiveConversationId } : {}),
387+
...(typeof depth === 'number' ? { depth } : {}),
388+
...(session?.user?.name && { userName: session.user.name }),
389+
}
390+
391+
// Log the payload being sent to the streaming endpoint
392+
try {
393+
logger.info(`[${tracker.requestId}] Sending payload to sim agent streaming endpoint`, {
394+
url: `${SIM_AGENT_API_URL}/api/chat-completion-streaming`,
395+
provider: providerToUse,
396+
mode,
397+
stream,
398+
workflowId,
399+
hasConversationId: !!effectiveConversationId,
400+
depth: typeof depth === 'number' ? depth : undefined,
401+
messagesCount: requestPayload.messages.length,
402+
})
403+
// Full payload as JSON string
404+
logger.info(
405+
`[${tracker.requestId}] Full streaming payload: ${JSON.stringify(requestPayload)}`
406+
)
407+
} catch (e) {
408+
logger.warn(`[${tracker.requestId}] Failed to log payload preview for streaming endpoint`, e)
409+
}
375410

376411
const simAgentResponse = await fetch(`${SIM_AGENT_API_URL}/api/chat-completion-streaming`, {
377412
method: 'POST',
378413
headers: {
379414
'Content-Type': 'application/json',
380415
...(env.COPILOT_API_KEY ? { 'x-api-key': env.COPILOT_API_KEY } : {}),
381416
},
382-
body: JSON.stringify({
383-
messages: messagesForAgent,
384-
workflowId,
385-
userId: authenticatedUserId,
386-
stream: stream,
387-
streamToolCalls: true,
388-
mode: mode,
389-
provider: providerToUse,
390-
...(effectiveConversationId ? { conversationId: effectiveConversationId } : {}),
391-
...(typeof depth === 'number' ? { depth } : {}),
392-
...(session?.user?.name && { userName: session.user.name }),
393-
}),
417+
body: JSON.stringify(requestPayload),
394418
})
395419

396420
if (!simAgentResponse.ok) {

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/diff-controls.tsx

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -196,22 +196,17 @@ export function DiffControls() {
196196
logger.warn('Failed to clear preview YAML:', error)
197197
})
198198

199-
// Accept changes with automatic backup and rollback on failure
200-
await acceptChanges()
199+
// Accept changes without blocking the UI; errors will be logged by the store handler
200+
acceptChanges().catch((error) => {
201+
logger.error('Failed to accept changes (background):', error)
202+
})
201203

202-
logger.info('Successfully accepted and saved workflow changes')
203-
// Show success feedback if needed
204+
logger.info('Accept triggered; UI will update optimistically')
204205
} catch (error) {
205206
logger.error('Failed to accept changes:', error)
206207

207-
// Show error notification to user
208-
// Note: The acceptChanges function has already rolled back the state
209208
const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred'
210-
211-
// You could add toast notification here
212209
console.error('Workflow update failed:', errorMessage)
213-
214-
// Optionally show user-facing error dialog
215210
alert(`Failed to save workflow changes: ${errorMessage}`)
216211
}
217212
}

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

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,12 @@ import {
1010
} from 'react'
1111
import {
1212
ArrowUp,
13-
Boxes,
13+
Brain,
1414
BrainCircuit,
15-
BrainCog,
1615
Check,
1716
FileText,
1817
Image,
18+
Infinity as InfinityIcon,
1919
Loader2,
2020
MessageCircle,
2121
Package,
@@ -435,14 +435,14 @@ const UserInput = forwardRef<UserInputRef, UserInputProps>(
435435
}
436436

437437
const getDepthLabel = () => {
438-
if (agentDepth === 0) return 'Lite'
438+
if (agentDepth === 0) return 'Fast'
439439
if (agentDepth === 1) return 'Auto'
440440
if (agentDepth === 2) return 'Pro'
441441
return 'Max'
442442
}
443443

444444
const getDepthLabelFor = (value: 0 | 1 | 2 | 3) => {
445-
if (value === 0) return 'Lite'
445+
if (value === 0) return 'Fast'
446446
if (value === 1) return 'Auto'
447447
if (value === 2) return 'Pro'
448448
return 'Max'
@@ -459,9 +459,9 @@ const UserInput = forwardRef<UserInputRef, UserInputProps>(
459459

460460
const getDepthIconFor = (value: 0 | 1 | 2 | 3) => {
461461
if (value === 0) return <Zap className='h-3 w-3 text-muted-foreground' />
462-
if (value === 1) return <Boxes className='h-3 w-3 text-muted-foreground' />
463-
if (value === 2) return <BrainCircuit className='h-3 w-3 text-muted-foreground' />
464-
return <BrainCog className='h-3 w-3 text-muted-foreground' />
462+
if (value === 1) return <InfinityIcon className='h-3 w-3 text-muted-foreground' />
463+
if (value === 2) return <Brain className='h-3 w-3 text-muted-foreground' />
464+
return <BrainCircuit className='h-3 w-3 text-muted-foreground' />
465465
}
466466

467467
const getDepthIcon = () => getDepthIconFor(agentDepth)
@@ -654,7 +654,7 @@ const UserInput = forwardRef<UserInputRef, UserInputProps>(
654654
)}
655655
>
656656
<span className='flex items-center gap-1.5'>
657-
<Boxes className='h-3 w-3 text-muted-foreground' />
657+
<InfinityIcon className='h-3 w-3 text-muted-foreground' />
658658
Auto
659659
</span>
660660
{agentDepth === 1 && (
@@ -682,7 +682,7 @@ const UserInput = forwardRef<UserInputRef, UserInputProps>(
682682
>
683683
<span className='flex items-center gap-1.5'>
684684
<Zap className='h-3 w-3 text-muted-foreground' />
685-
Lite
685+
Fast
686686
</span>
687687
{agentDepth === 0 && (
688688
<Check className='h-3 w-3 text-muted-foreground' />
@@ -709,7 +709,7 @@ const UserInput = forwardRef<UserInputRef, UserInputProps>(
709709
)}
710710
>
711711
<span className='flex items-center gap-1.5'>
712-
<BrainCircuit className='h-3 w-3 text-muted-foreground' />
712+
<Brain className='h-3 w-3 text-muted-foreground' />
713713
Pro
714714
</span>
715715
{agentDepth === 2 && (
@@ -737,7 +737,7 @@ const UserInput = forwardRef<UserInputRef, UserInputProps>(
737737
)}
738738
>
739739
<span className='flex items-center gap-1.5'>
740-
<BrainCog className='h-3 w-3 text-muted-foreground' />
740+
<BrainCircuit className='h-3 w-3 text-muted-foreground' />
741741
Max
742742
</span>
743743
{agentDepth === 3 && (

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/components/file-selector/file-selector-input.tsx

Lines changed: 55 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ interface FileSelectorInputProps {
3434
disabled: boolean
3535
isPreview?: boolean
3636
previewValue?: any | null
37+
previewContextValues?: Record<string, any>
3738
}
3839

3940
export function FileSelectorInput({
@@ -42,13 +43,31 @@ export function FileSelectorInput({
4243
disabled,
4344
isPreview = false,
4445
previewValue,
46+
previewContextValues,
4547
}: FileSelectorInputProps) {
4648
const { getValue } = useSubBlockStore()
4749
const { collaborativeSetSubblockValue } = useCollaborativeWorkflow()
4850
const { activeWorkflowId } = useWorkflowRegistry()
4951
const params = useParams()
5052
const workflowIdFromUrl = (params?.workflowId as string) || activeWorkflowId || ''
5153

54+
// Helper to coerce various preview value shapes into a string ID
55+
const coerceToIdString = (val: unknown): string => {
56+
if (!val) return ''
57+
if (typeof val === 'string') return val
58+
if (typeof val === 'number') return String(val)
59+
if (typeof val === 'object') {
60+
const obj = val as Record<string, any>
61+
return (obj.id ||
62+
obj.fileId ||
63+
obj.value ||
64+
obj.documentId ||
65+
obj.spreadsheetId ||
66+
'') as string
67+
}
68+
return ''
69+
}
70+
5271
// Use the proper hook to get the current value and setter
5372
const [storeValue, setStoreValue] = useSubBlockValue(blockId, subBlock.id)
5473
const [selectedFileId, setSelectedFileId] = useState<string>('')
@@ -108,19 +127,37 @@ export function FileSelectorInput({
108127
const isMicrosoftSharePoint = provider === 'microsoft' && subBlock.serviceId === 'sharepoint'
109128
const isMicrosoftPlanner = provider === 'microsoft-planner'
110129
// For Confluence and Jira, we need the domain and credentials
111-
const domain = isConfluence || isJira ? (getValue(blockId, 'domain') as string) || '' : ''
112-
const jiraCredential = isJira ? (getValue(blockId, 'credential') as string) || '' : ''
130+
const domain =
131+
isConfluence || isJira
132+
? (isPreview && previewContextValues?.domain?.value) ||
133+
(getValue(blockId, 'domain') as string) ||
134+
''
135+
: ''
136+
const jiraCredential = isJira
137+
? (isPreview && previewContextValues?.credential?.value) ||
138+
(getValue(blockId, 'credential') as string) ||
139+
''
140+
: ''
113141
// For Discord, we need the bot token and server ID
114-
const botToken = isDiscord ? (getValue(blockId, 'botToken') as string) || '' : ''
115-
const serverId = isDiscord ? (getValue(blockId, 'serverId') as string) || '' : ''
142+
const botToken = isDiscord
143+
? (isPreview && previewContextValues?.botToken?.value) ||
144+
(getValue(blockId, 'botToken') as string) ||
145+
''
146+
: ''
147+
const serverId = isDiscord
148+
? (isPreview && previewContextValues?.serverId?.value) ||
149+
(getValue(blockId, 'serverId') as string) ||
150+
''
151+
: ''
116152

117153
// Use preview value when in preview mode, otherwise use store value
118154
const value = isPreview ? previewValue : storeValue
119155

120156
// Keep local selection in sync with store value (and preview)
121157
useEffect(() => {
122-
const effective = isPreview && previewValue !== undefined ? previewValue : storeValue
123-
if (typeof effective === 'string' && effective !== '') {
158+
const raw = isPreview && previewValue !== undefined ? previewValue : storeValue
159+
const effective = coerceToIdString(raw)
160+
if (effective) {
124161
if (isJira) {
125162
setSelectedIssueId(effective)
126163
} else if (isDiscord) {
@@ -385,7 +422,7 @@ export function FileSelectorInput({
385422
<TooltipTrigger asChild>
386423
<div className='w-full'>
387424
<MicrosoftFileSelector
388-
value={selectedFileId}
425+
value={coerceToIdString(selectedFileId)}
389426
onChange={handleFileChange}
390427
provider='microsoft-excel'
391428
requiredScopes={subBlock.requiredScopes || []}
@@ -418,7 +455,7 @@ export function FileSelectorInput({
418455
<TooltipTrigger asChild>
419456
<div className='w-full'>
420457
<MicrosoftFileSelector
421-
value={selectedFileId}
458+
value={coerceToIdString(selectedFileId)}
422459
onChange={handleFileChange}
423460
provider='microsoft-word'
424461
requiredScopes={subBlock.requiredScopes || []}
@@ -450,7 +487,7 @@ export function FileSelectorInput({
450487
<TooltipTrigger asChild>
451488
<div className='w-full'>
452489
<MicrosoftFileSelector
453-
value={selectedFileId}
490+
value={coerceToIdString(selectedFileId)}
454491
onChange={handleFileChange}
455492
provider='microsoft'
456493
requiredScopes={subBlock.requiredScopes || []}
@@ -482,7 +519,7 @@ export function FileSelectorInput({
482519
<TooltipTrigger asChild>
483520
<div className='w-full'>
484521
<MicrosoftFileSelector
485-
value={selectedFileId}
522+
value={coerceToIdString(selectedFileId)}
486523
onChange={handleFileChange}
487524
provider='microsoft'
488525
requiredScopes={subBlock.requiredScopes || []}
@@ -662,11 +699,9 @@ export function FileSelectorInput({
662699
// Default to Google Drive picker
663700
return (
664701
<GoogleDrivePicker
665-
value={
666-
(isPreview && previewValue !== undefined
667-
? (previewValue as string)
668-
: (storeValue as string)) || ''
669-
}
702+
value={coerceToIdString(
703+
(isPreview && previewValue !== undefined ? previewValue : storeValue) as any
704+
)}
670705
onChange={(val, info) => {
671706
setSelectedFileId(val)
672707
setFileInfo(info || null)
@@ -682,7 +717,11 @@ export function FileSelectorInput({
682717
onFileInfoChange={setFileInfo}
683718
clientId={clientId}
684719
apiKey={apiKey}
685-
credentialId={(getValue(blockId, 'credential') as string) || ''}
720+
credentialId={
721+
((isPreview && previewContextValues?.credential?.value) ||
722+
(getValue(blockId, 'credential') as string) ||
723+
'') as string
724+
}
686725
workflowId={workflowIdFromUrl}
687726
/>
688727
)

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/components/long-input.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -389,6 +389,8 @@ export function LongInput({
389389
fontFamily: 'inherit',
390390
lineHeight: 'inherit',
391391
height: `${height}px`,
392+
wordBreak: 'break-word',
393+
whiteSpace: 'pre-wrap',
392394
}}
393395
/>
394396
<div
@@ -397,7 +399,7 @@ export function LongInput({
397399
style={{
398400
fontFamily: 'inherit',
399401
lineHeight: 'inherit',
400-
width: textareaRef.current ? `${textareaRef.current.clientWidth}px` : '100%',
402+
width: '100%',
401403
height: `${height}px`,
402404
overflow: 'hidden',
403405
}}

0 commit comments

Comments
 (0)