Techniques
Undo
Copilot.js integrates seamlessly with your app's undo logic, regardless of the library you use: Redux Undo, Zundo, or any other. In this section, we use Zundo in all examples. Try the live demo to see how the undo integration works.
Group assistant actions in single undo step
It is best UX practice to let your users undo the actions performed by the assistant all at once, rather than individually. For example:
To achieve this behavior, configure your app to work with Copilot as follows:
- Enrich your application's store with the
copilotSnapshot
.
// store.js
import { create } from 'zustand'
import { temporal } from 'zundo'
import { temporalOptions } from './options'
const useDrawStore = create(
temporal(
(set) => ({
shapes: [], // Your application's state.
copilotSnapshot: { isRunning: false }, // Add the `copilotSnapshot` with an initial value.
setCopilotSnapshot: (newSnapshot) => set({ copilotSnapshot: newSnapshot }),
}),
temporalOptions,
),
)
- Keep the
copilotSnapshot
up-to-date inside your store, using thesnapshotChange
event.
// App.jsx
import { CopilotProvider, CopilotChat } from '@copilotjs/react'
import '@copilotjs/styles/default.css'
export default function App() {
const { setCopilotSnapshot } = useDrawStore()
return (
<CopilotProvider
// When the Copilot snapshot changes, update `copilotSnapshot` in your store.
onSnapshotChange={(event) => setCopilotSnapshot(event.newSnapshot)}
// ...
>
<CopilotChat />
</CopilotProvider>
)
}
- Save the app state when the Copilot transitions to
running
state. This lets the user undo all subsequent actions performed by the assistant, in a single undo step.
// options.js
export const temporalOptions = {
// Save the state when Copilot changes to 'running' state.
equality: (pastState, currentState) => {
const isDifferent =
(!pastState.copilotSnapshot.isRunning && currentState.copilotSnapshot.isRunning) ||
(!currentState.copilotSnapshot.isRunning && pastState.shapes !== currentState.shapes)
return !isDifferent
},
// Include `copilotSnapshot` in undo history.
partialize: (state) => {
const { shapes, copilotSnapshot } = state
return { shapes, copilotSnapshot }
},
}
Enable undo directly in chat UI
By default, Copilot defers the "UX of undo" to your app. This means using the buttons and shortcuts already provided by your app to undo actions performed by the assistant.
However, it can be more intuitive for the user to undo an action directly in the chat UI:
To enable this feature:
-
Enrich your application's store with the
copilotSnapshot
, as done in the previous section. -
Set the props of
CopilotProvider
andCopilotChat
as follows:
// App.jsx
import { useDrawStore, useTemporalStore } from './store'
import { temporalOptions } from './options'
import { CopilotProvider, CopilotChat } from '@copilotjs/react'
import '@copilotjs/styles/default.css'
export default function App() {
const drawStore = useDrawStore()
const { pastStates, futureStates, undo, redo } = useTemporalStore((state) => state)
return (
<CopilotProvider
// Pass your app's undo history to Copilot.
context={{
undoHistory: {
past: pastStates,
present: temporalOptions.partialize(drawStore),
future: futureStates,
},
}}
// ...
>
<CopilotChat
// Show the undo & redo buttons in the Copilot chat UI.
appearance={{
enableUndo: true,
}}
// When the user presses the undo & redo buttons in the Copilot chat UI,
// invoke the corresponding functions in your undo library.
onUndo={() => undo()}
onRedo={() => redo()}
/>
</CopilotProvider>
)
}
Note that unlike the context.state
, which must be kept small, the context.undoHistory
can be as large as needed, because it is not sent to the external AI provider.