Diff view

This commit is contained in:
Siddharth Ganesan
2026-01-09 18:26:29 -08:00
committed by Emir Karabeg
parent 8597786962
commit 0ba5ec65f7
2 changed files with 81 additions and 73 deletions

View File

@@ -206,54 +206,47 @@ export const DiffControls = memo(function DiffControls() {
}
}, [activeWorkflowId, currentChat, messages, baselineWorkflow])
const handleAccept = useCallback(async () => {
const handleAccept = useCallback(() => {
logger.info('Accepting proposed changes with backup protection')
// Resolve target toolCallId for build/edit and update to terminal success state in the copilot store
// This happens synchronously first for instant UI feedback
try {
// Create a checkpoint before applying changes so it appears under the triggering user message
await createCheckpoint().catch((error) => {
logger.warn('Failed to create checkpoint before accept:', error)
})
// Resolve target toolCallId for build/edit and update to terminal success state in the copilot store
try {
const { toolCallsById, messages } = useCopilotStore.getState()
let id: string | undefined
outer: for (let mi = messages.length - 1; mi >= 0; mi--) {
const m = messages[mi]
if (m.role !== 'assistant' || !m.contentBlocks) continue
const blocks = m.contentBlocks as any[]
for (let bi = blocks.length - 1; bi >= 0; bi--) {
const b = blocks[bi]
if (b?.type === 'tool_call') {
const tn = b.toolCall?.name
if (tn === 'edit_workflow') {
id = b.toolCall?.id
break outer
}
const { toolCallsById, messages } = useCopilotStore.getState()
let id: string | undefined
outer: for (let mi = messages.length - 1; mi >= 0; mi--) {
const m = messages[mi]
if (m.role !== 'assistant' || !m.contentBlocks) continue
const blocks = m.contentBlocks as any[]
for (let bi = blocks.length - 1; bi >= 0; bi--) {
const b = blocks[bi]
if (b?.type === 'tool_call') {
const tn = b.toolCall?.name
if (tn === 'edit_workflow') {
id = b.toolCall?.id
break outer
}
}
}
if (!id) {
const candidates = Object.values(toolCallsById).filter((t) => t.name === 'edit_workflow')
id = candidates.length ? candidates[candidates.length - 1].id : undefined
}
if (id) updatePreviewToolCallState('accepted', id)
} catch {}
}
if (!id) {
const candidates = Object.values(toolCallsById).filter((t) => t.name === 'edit_workflow')
id = candidates.length ? candidates[candidates.length - 1].id : undefined
}
if (id) updatePreviewToolCallState('accepted', id)
} catch {}
// Accept changes without blocking the UI; errors will be logged by the store handler
acceptChanges().catch((error) => {
logger.error('Failed to accept changes (background):', error)
})
// Accept changes without blocking the UI; errors will be logged by the store handler
acceptChanges().catch((error) => {
logger.error('Failed to accept changes (background):', error)
})
logger.info('Accept triggered; UI will update optimistically')
} catch (error) {
logger.error('Failed to accept changes:', error)
// Create checkpoint in the background (fire-and-forget) so it doesn't block UI
createCheckpoint().catch((error) => {
logger.warn('Failed to create checkpoint after accept:', error)
})
const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred'
logger.error('Workflow update failed:', errorMessage)
alert(`Failed to save workflow changes: ${errorMessage}`)
}
logger.info('Accept triggered; UI will update optimistically')
}, [createCheckpoint, updatePreviewToolCallState, acceptChanges])
const handleReject = useCallback(() => {

View File

@@ -433,6 +433,7 @@ export const useWorkflowDiffStore = create<WorkflowDiffState & WorkflowDiffActio
)
}
// Background operations (fire-and-forget) - don't block
if (triggerMessageId) {
fetch('/api/copilot/stats', {
method: 'POST',
@@ -445,14 +446,13 @@ export const useWorkflowDiffStore = create<WorkflowDiffState & WorkflowDiffActio
}).catch(() => {})
}
const toolCallId = await findLatestEditWorkflowToolCallId()
if (toolCallId) {
try {
await getClientTool(toolCallId)?.handleAccept?.()
} catch (error) {
logger.warn('Failed to notify tool accept state', { error })
findLatestEditWorkflowToolCallId().then((toolCallId) => {
if (toolCallId) {
getClientTool(toolCallId)?.handleAccept?.()?.catch?.((error: Error) => {
logger.warn('Failed to notify tool accept state', { error })
})
}
}
})
},
rejectChanges: async () => {
@@ -487,27 +487,26 @@ export const useWorkflowDiffStore = create<WorkflowDiffState & WorkflowDiffActio
})
const afterReject = cloneWorkflowState(baselineWorkflow)
// Clear diff state FIRST for instant UI feedback
set({
hasActiveDiff: false,
isShowingDiff: false,
isDiffReady: false,
baselineWorkflow: null,
baselineWorkflowId: null,
diffAnalysis: null,
diffMetadata: null,
diffError: null,
_triggerMessageId: null,
})
// Clear the diff engine
diffEngine.clearDiff()
// Apply baseline state locally
applyWorkflowStateToStores(baselineWorkflowId, baselineWorkflow)
// Broadcast to other users
logger.info('Broadcasting reject to other users', {
workflowId: activeWorkflowId,
blockCount: Object.keys(baselineWorkflow.blocks).length,
})
await enqueueReplaceWorkflowState({
workflowId: activeWorkflowId,
state: baselineWorkflow,
})
// Persist to database
const persisted = await persistWorkflowStateToServer(baselineWorkflowId, baselineWorkflow)
if (!persisted) {
throw new Error('Failed to restore baseline workflow state')
}
// Emit event for undo/redo recording
// Emit event for undo/redo recording synchronously
if (!(window as any).__skipDiffRecording) {
window.dispatchEvent(
new CustomEvent('record-diff-operation', {
@@ -522,6 +521,25 @@ export const useWorkflowDiffStore = create<WorkflowDiffState & WorkflowDiffActio
)
}
// Background operations (fire-and-forget) - don't block UI
// Broadcast to other users
logger.info('Broadcasting reject to other users', {
workflowId: activeWorkflowId,
blockCount: Object.keys(baselineWorkflow.blocks).length,
})
enqueueReplaceWorkflowState({
workflowId: activeWorkflowId,
state: baselineWorkflow,
}).catch((error) => {
logger.error('Failed to broadcast reject to other users:', error)
})
// Persist to database in background
persistWorkflowStateToServer(baselineWorkflowId, baselineWorkflow).catch((error) => {
logger.error('Failed to persist baseline workflow state:', error)
})
if (_triggerMessageId) {
fetch('/api/copilot/stats', {
method: 'POST',
@@ -534,16 +552,13 @@ export const useWorkflowDiffStore = create<WorkflowDiffState & WorkflowDiffActio
}).catch(() => {})
}
const toolCallId = await findLatestEditWorkflowToolCallId()
if (toolCallId) {
try {
await getClientTool(toolCallId)?.handleReject?.()
} catch (error) {
logger.warn('Failed to notify tool reject state', { error })
findLatestEditWorkflowToolCallId().then((toolCallId) => {
if (toolCallId) {
getClientTool(toolCallId)?.handleReject?.()?.catch?.((error: Error) => {
logger.warn('Failed to notify tool reject state', { error })
})
}
}
get().clearDiff({ restoreBaseline: false })
})
},
reapplyDiffMarkers: () => {