0.0.39 • Published 6 days ago

@emanation/pacific v0.0.39

Weekly downloads
-
License
ISC
Repository
-
Last release
6 days ago

Emanation AI Figma Plugin SDK

This is a prerelease version of the Emanation AI Figma Plugin SDK. Here be dragons.

Architecture:

The "SDK" is comprised of two parts: The API and the library. The API is simply an endpoint that accepts an authorization header and body. The body consists of three required parameters: prompt, temperature, threshold. Upon success, you will receive an arbitrary JSON payload that you hand off to the library. The library will then paint the JSON payload to the Figma canvas. Additionally, when painting to the canvas, you can fine-tune the timeout delay.

Steps:

  1. Install the Figma plugin library (this package: @emanation/pacific) into your Figma plugin project.
  2. Make a POST request to the Emanation AI endpoint with your prompt and prompt configuration.
  3. Call the library to handle rendering to the Figma canvas

Step 1: Install the Figma plugin library

Install the Figma plugin library by one of the following commands:

npm i @emanation/pacific
# Or yarn add @emanation/pacific

Step 2: Make a POST request with the Emanation AI endpoints

The endpoint is https://api.uidesign.ai/gen/v1/design/bravo/ Note that the trailing / is required.

When making fetch requests in a Figma plugin, note that Figma may block outgoing and incoming requests due to plugin permissions. See networkAccess for reference. In short, you may need to whitelist https://api.uidesign.ai/gen/v1/design/bravo/ in your Figma plugin's manifest.json file.

The fetch request should look something like this:

const response = await fetch("https://api.uidesign.ai/gen/v1/design/bravo/", {
  method:  "POST",
  headers: {
    "X-API-Key":    "foo-bar-baz-123", // <- Your API key
    "Content-Type": "application/json",
  },
  body: JSON.stringify(body),
})
// Hand off the result to the Figma plugin library

Finally, the shape of body is as follows:

export type DownlodRequestBody = {
  prompt:      string
  temperature: number // A floating point number between 0-1
  threshold:   number // A floating point number between 0-1
}

Some examples of prompts:

  • a personal finance app for managing your wallet
  • a travel based social media app
  • a minimal, eco-friendly e-commerce app

Note that prompts may be short or long.

For the time being, you may set temperature and threshold to 0.

Please plan for one of the following status codes:

  • 200: Success
  • 403: Unauthorized
  • 422: Unprocessable Entity
  • 503: Internal Server Error

This is how we internally test https://api.uidesign.ai/gen/v2/design/bravo/ for reference:

import * as z from "zod"
// ...

export type DownlodRequestBody = {
  prompt:      string
  temperature: number
  threshold:   number
}

const DownloadResponseScreenSchema = z.object({
  public: z.boolean(),

  name:        z.string(),
  description: z.string(),
  tags:        z.array(z.string()).nullable(),
  url:         z.string().nullable(),
  img_url:     z.string().nullable(),
  root:        z.any(),
  meta:        z.record(z.any()),
})

const DownloadResponseScreensSchema = z.object({
  screens: z.array(DownloadResponseScreenSchema),
})

export type DownloadResponse = z.infer<typeof DownloadResponseScreensSchema>
export async function getEmanationPayload(body: EmanationSDKBody): Promise<
  | [StatusOK, EmanationResponse]
  | [
    | StatusNetworkError
    | StatusBadRequest
    | StatusForbidden
    | StatusUnprocessableEntity
    | StatusResponseServerError
    | StatusInternalError,
    null
  ]
> {
  let response: Response
  try {
    response = await fetch("https://api.uidesign.ai/gen/v2/design/bravo/", {
      method:  "POST",
      headers: {
        "X-API-Key":    "foo-bar-baz-123", // <- Your API key
        "Content-Type": "application/json",
      },
      body: JSON.stringify(body), // <- Your prompt, temperature, and threshold (object)
    })
  } catch (error) {
    return [statusNetworkError, null]
  }
  switch (response.status) {
    case 200: {
      const body = await response.json()
      const result = DownloadResponseScreensSchema.safeParse(body)
      if (result.success) {
        return [statusOK, result.data]
      } else {
        console.error(result.error)
        return [statusBadRequest, null]
      }
    }
    case 403: return [statusForbidden, null]
    case 422: return [statusUnprocessableEntity, null]
    case 503: return [statusResponseServerError, null]
    default:  return [statusInternalError, null]
  }
}

Note that Zod validation is not required. Your implementation simply needs to make the fetch request with the correct headers and body and hand off to the Figma library.

Step 3: Call the Figma library to handle rendering to the Figma canvas

The library exports one function you will need to hand off the arbitrary JSON payload to: drawScreens. This is an asynchronous function accepts the JSON payload and a configuration object that accepts timeout. You don't need to do anything to the JSON payload except forward it as is as the first argument to drawScreens. The second argument accepts timeout which controls the drawing speed. For shorter drawings, set timeout to 0. For longer drawings, set timeout to 50, 100, or 200 for example. Internally, we found about 50-75ms feels right.

Note that even if you set timeout to 0, there will be a slight delay.

Finally, we recommend adding some additional code before and after calling drawScreens:

  • Notify the user they are making a generation. For example, we recommend figma.notify(`Generating ${JSON.stringify(prompt)}`, { timeout: Number.POSITIVE_INFINITY }). This is because the fetch request may take 0.5-2s to complete.

  • Notify the user the generation is done. For example figma.notify(`Generated ${JSON.stringify(prompt)}!`). You don't need to set an explicit timeout for this notification because it's intended to be short lived.

For reference, here's how you might structure your code.ts:

figma.ui.onmessage = async (action: PostMessageToFigma) => {
  switch (action.type) {
    // ...
    case "DRAW_SCREENS": {
      const handler = figma.notify(`Generating ${JSON.stringify(prompt)}`, { timeout: Number.POSITIVE_INFINITY })
      await drawScreens(response, { timeout: 50 })
      handler.cancel()
      figma.notify(`Generated ${JSON.stringify(prompt)}!`)
      break
    }
    // ...
  }
}

Ideally, the figma.notify(`Generating ${JSON.stringify(prompt)}`, { timeout: Number.POSITIVE_INFINITY }) should actually before the fetch request, not after. This is because the fetch request may take 0.5-2s to resolve. We use the following code so that ui.html may dispatch controlled notifications to code.ts:

// notify.ts
let _handler: NotificationHandler | null = null

export function clearNotify(): void {
  if (_handler !== null) _handler.cancel()
}

export function notify(
  message:     string,
  { timeout }: { timeout?: number } = { timeout: 5_000 },
): NotificationHandler {
  if (_handler !== null) _handler.cancel()
  console.log(`[notify] ${JSON.stringify(message)}`)
  _handler = figma.notify(message, { timeout })
  return _handler
}

export function notifyError(
  message:     string,
  { timeout }: { timeout?: number } = { timeout: 10_000 },
): NotificationHandler {
  if (_handler !== null) _handler.cancel()
  console.error(`[notifyError] ${JSON.stringify(message)}`)
  _handler = figma.notify(message, { timeout, error: true })
  return _handler
}

// code.ts
figma.ui.onmessage = async (action: PostMessageToFigma) => {
  switch (action.type) {
    case "NOTIFY":
      notify(action.payload.message, {
        timeout: action.payload.timeout,
      })
      break
    case "NOTIFY_ERROR":
      notifyError(action.payload.message, {
        timeout: action.payload.timeout,
      })
      break
    // ...
  }
}

Then we can simply write the following in ui.html to communicate as eagerly as possible:

// ...
window.parent.postMessage({
  pluginMessage: {
    type:    "NOTIFY",
    payload: {
      message: `Generating ${JSON.stringify(prompt)}...`,
      timeout: Number.POSITIVE_INFINITY,
    },
  },
}, "*")
// ...
0.0.38

6 days ago

0.0.39

6 days ago

0.0.37

22 days ago

0.0.34

3 months ago

0.0.35

3 months ago

0.0.36

3 months ago

0.0.32

3 months ago

0.0.33

3 months ago

0.0.31

3 months ago

0.0.30

3 months ago

0.0.28

3 months ago

0.0.29

3 months ago

0.0.27

3 months ago

0.0.26

3 months ago

0.0.25

4 months ago

0.0.24

4 months ago

0.0.23

4 months ago

0.0.22

4 months ago

0.0.21

4 months ago

0.0.20

4 months ago

0.0.19

4 months ago

0.0.18

4 months ago

0.0.16

4 months ago

0.0.17

4 months ago

0.0.1-6.3

4 months ago

0.0.1-6.2

4 months ago

0.0.1-6.1

4 months ago

0.0.1-6.6

4 months ago

0.0.1-6.5

4 months ago

0.0.1-6.4

4 months ago

0.0.15

4 months ago

0.0.14

6 months ago

0.0.13

6 months ago

0.0.12

6 months ago

0.0.11

6 months ago

0.0.10

6 months ago

0.0.9

6 months ago

0.0.8

6 months ago

0.0.7

7 months ago

0.0.6

8 months ago

0.0.5

8 months ago

0.0.4

8 months ago

0.0.3

8 months ago

0.0.2

8 months ago

0.0.1

8 months ago

0.0.0

8 months ago