Menu

Techniques

Actions

A key capability of Copilot.js is to perform actions in your frontend app, as directed by the user. For example, the user can ask:

  • "Insert an image that matches the text."
  • "Rename all files to lowercase."
  • "Translate the selection to Spanish."

The principle

Consider the example of a drawing app, which has CRUD functions for manipulating shapes, such as createShape, updateShape, and so on. These functions are typically implemented inside a frontend store, using Redux or Zustand.

To perform a user action, the copilot invokes the appropriate functions in your app's store:

Copilot invokes actions in your app

Enabling actions

  1. Write type definitions for the actions that you'll be exposing to Copilot. In our drawing app example, let's create a separate file:

    // actionTypes.ts
    export const actionTypes = `
    // -------------------- Actions --------------------
    type createShape = (shape: Shape) => ShapeWithId
    type updateShape = (shapeId: string, updatedShape: Partial<Shape>) => void
    type deleteShape = (shapeId: string) => void
    type getShape = (shapeId: string) => ShapeWithId
    type listShapes = (filters: Partial<ShapeWithId>) => ShapeWithId[]
    
    // -------------------- Shapes --------------------
    type Shape = Square | Circle
    type Circle = { type: 'circle'; x: number; y: number; radius: number }
    type Square = { type: 'square'; x: number; y: number; length: number }
    
    // Extend shapes with IDs
    type ShapeWithId = SquareWithId | CircleWithId
    type CircleWithId = Circle & { id: string }
    type SquareWithId = Square & { id: string }
    `
    

    There might be a simpler way to import your action types, depending on your bundler.

  2. Supply the actionTypes and actions to the <CopilotProvider>'s context:

    // App.jsx
    import { actionTypes } from './actionTypes'
    import store from './store' // Your application's store.
    import { CopilotProvider } from '@copilotjs/react'
    import { Copilot } from '@/components/Copilot'
    
    export default function App() {
      return (
        <CopilotProvider
          context={{
            // A string with type definitions of exposed actions.
            actionTypes: actionTypes,
            // A map from each action's name to its function.
            actions: {
              createShape: store.createShape,
              updateShape: store.updateShape,
              deleteShape: store.deleteShape,
              getShape: store.getShape,
              listShapes: store.listShapes,
            },
          }}
          // ...
        >
          <Copilot className="h-[600px] w-[400px]" />
        </CopilotProvider>
      )
    }
    

Now, <CopilotProvider> can invoke actions in your app's store:

Copilot invoking actions in your app

Importing action types

The best way to import your action's TypeScript definitions depends on your bundler.

With Vite

With Vite, you can keep a separate actionTypes.ts file (with the benefit of syntax highlighting), and use the ?raw suffix to conveniently import the file contents as a string:

// actionTypes.ts
type createShape = ...
// App.jsx
import actionTypes from './actionTypes.ts?raw' // Import the whole file as raw string.
...
<CopilotProvider
  context={{ actionTypes: actionTypes }}
  // ...
/>

With any bundler

A universal method that works with any bundler is to store the type definitions in a plain string, then import it as a regular variable. However, you lose the syntax highlighting for the TypeScript definitions.

// actionTypes.ts
export const actionTypes = `
type createShape = ...
`
// App.jsx
import { actionTypes } from './actionTypes' // Import the string variable.
...
<CopilotProvider
  context={{ actionTypes: actionTypes }}
  // ...
/>
Previous
Knowledge
Next
State