npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

epixa-canvas

v1.0.4

Published

基于 Vue Flow 的无限画布工作流 SDK,支持 AI 文本/图片/视频生成节点编排

Readme

无限画布集成指南 (Infinite Canvas Integration Guide)

本指南旨在帮助您将此 无限画布 (WorkflowCanvas) 快速集成到您的 Vue 3 业务项目中,并完成画布数据的持久化(保存、历史记录列表选择与读取、删除)。


📦 1. 快速上手

步骤 0:安装 SDK 包

在您的项目中安装本无限画布 SDK:

# 使用 pnpm
pnpm add epixa-canvas

# 使用 npm
npm install epixa-canvas

步骤 A:安装核心对等依赖

我们的画布核心基于 @vue-flow 开发,请确保您的项目安装了以下必需依赖:

# 使用 pnpm 安装
pnpm add @vue-flow/core @vue-flow/background @vue-flow/controls @vue-flow/minimap @vue-flow/node-resizer lucide-vue-next pinia

# 或者使用 npm 安装
npm install @vue-flow/core @vue-flow/background @vue-flow/controls @vue-flow/minimap @vue-flow/node-resizer lucide-vue-next pinia --save

步骤 B:引入组件与样式

在您的业务页面中直接导入画布组件:

<template>
  <div class="canvas-container">
    <WorkflowCanvas
      :initial-title="canvasData.title"
      :initial-nodes="canvasData.nodes"
      :initial-edges="canvasData.edges"
      :initial-viewport="canvasData.viewport"
      :history-list="historyList"
      :current-history-id="currentCanvasId"
      :api-config="apiConfig"
      @save="handleSave"
      @load-history="handleLoadHistory"
      @delete-history="handleDeleteHistory"
      @workflow-change="handleWorkflowChange"
    />
  </div>
</template>

<script setup>
import { ref, onMounted } from 'vue'
import { WorkflowCanvas } from 'epixa-canvas'
// 注意:必须显式导入库的样式文件
import 'epixa-canvas/dist/style.css'

// 1. 配置 AI 接口与鉴权(解耦硬编码后,由宿主系统动态注入)
const apiConfig = {
  baseUrl: 'https://api.yourdomain.com/v1',
  cdnUrl: 'https://cdn.yourdomain.com',
  headers: {
    'Authorization': 'Bearer YOUR_USER_TOKEN' // 宿主系统中当前登录用户的鉴权 Token
  }
}

// 2. 当前激活的画布状态与 ID
const currentCanvasId = ref(123)
const canvasData = ref({
  title: '我的新工作流画布',
  nodes: [],
  edges: [],
  viewport: { x: 0, y: 0, zoom: 1 }
})

// 3. 传入画布的历史记录列表数据
const historyList = ref([])

// 初始化加载数据
onMounted(async () => {
  await fetchCanvasDetail(currentCanvasId.value)
  await fetchHistoryList()
})

// 从后端获取当前画布详情并渲染
const fetchCanvasDetail = async (id) => {
  const response = await fetch(`/api/canvas/detail?id=${id}`)
  const result = await response.json()
  if (result.success) {
    currentCanvasId.value = result.data.id
    canvasData.value = {
      title: result.data.title,
      nodes: result.data.canvasData.nodes || [],
      edges: result.data.canvasData.edges || [],
      viewport: result.data.canvasData.viewport || { x: 0, y: 0, zoom: 1 }
    }
  }
}

// 获取已保存画布的历史记录列表
const fetchHistoryList = async () => {
  const response = await fetch('/api/canvas/history-list')
  const result = await response.json()
  if (result.success) {
    historyList.value = result.data.list // 数组,每个元素包含 id, title, updatedAt, nodes, edges, viewport
  }
}

// 监听保存事件(命名后触发)
const handleSave = async (data) => {
  console.log('保存画布:', data)
  // data 结构: { title, nodes, edges, viewport }
  const response = await fetch('/api/canvas/save', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      id: currentCanvasId.value,
      title: data.title,
      canvas_data: {
        nodes: data.nodes,
        edges: data.edges,
        viewport: data.viewport
      }
    })
  })
  if (response.ok) {
    alert('保存成功!')
    await fetchHistoryList() // 刷新历史列表
  }
}

// 监听用户在“历史记录列表”中选中某一项的加载事件
const handleLoadHistory = (item) => {
  console.log('加载选中的历史版本:', item)
  currentCanvasId.value = item.id
  canvasData.value = {
    title: item.title,
    nodes: item.nodes,
    edges: item.edges,
    viewport: item.viewport
  }
}

// 监听用户删除某一条历史记录的事件
const handleDeleteHistory = async (id) => {
  const response = await fetch(`/api/canvas/delete-history?id=${id}`, {
    method: 'DELETE'
  })
  if (response.ok) {
    await fetchHistoryList() // 刷新历史列表
  }
}

// 监听画布实时变动(用于同步草稿等)
const handleWorkflowChange = (graph) => {
  console.log('画布内容实时变动:', graph)
}
</script>

<style scoped>
.canvas-container {
  width: 100vw;
  height: 100vh;
  background-color: #000;
}
</style>

🔌 2. API 参考 (Props & Events)

Props (输入属性)

| 属性名 | 类型 | 是否必传 | 默认值 | 说明 | | :--- | :--- | :--- | :--- | :--- | | initial-title | string | 否 | '未命名画布' | 初始载入的画布标题名称 | | initial-nodes | WorkflowNode[] | 否 | [] | 初始载入的节点列表 | | initial-edges | WorkflowEdge[] | 否 | [] | 初始载入的连接线列表 | | initial-viewport | { x: number, y: number, zoom: number } | 否 | { x: 0, y: 0, zoom: 1 } | 还原用户上次离开时的视口缩放与位移位置 | | history-list | SavedCanvas[] | 否 | [] | 在“历史弹窗”中显示的历史画布记录列表 | | current-history-id | string \| number | 否 | - | 当前激活/载入的历史画布 ID,激活的画布在列表中会有绿色勾选标识 | | readonly | boolean | 否 | false | 只读模式,开启后用户无法编辑画布 | | options | WorkflowOptions | 否 | - | 画布高级全局配置项(如支持的 AI 模型列表等) | | api-config | ApiConfig | 否 | - | 配置 AI 接口的 baseUrl、鉴权 headers 及 cdnUrl,详细数据格式参考第 5 节 |

Events (输出事件)

| 事件名 | 参数格式 | 说明 | | :--- | :--- | :--- | | @save | (data: { title: string, nodes: any[], edges: any[], viewport: any }) => void | 用户点击“保存”按钮或使用快捷键 Ctrl+S / Cmd+S,并在弹窗确认命名后触发 | | @load-history | (item: SavedCanvas) => void | 用户在历史弹窗中选择某项历史画布时触发,宿主项目应拦截并更新传入画布的数据 | | @delete-history | (id: string \| number) => void | 用户在历史弹窗点击“删除”按钮并二次确认后触发,宿主项目应调用后端接口删除该记录 | | @workflow-change | (data: { nodes: any[], edges: any[] }) => void | 画布节点、连线发生变化时实时触发的草稿同步事件(自带防抖合并) | | @node-click | (node: WorkflowNode) => void | 点击单个节点时触发 | | @node-add | (node: WorkflowNode) => void | 新增节点时触发 | | @node-delete | (nodeId: string) => void | 删除节点时触发 |


🗄️ 3. 数据保存与数据库字段设计建议

1. 历史画布数据结构 (SavedCanvas JSON)

history-list 数组里的每个历史元素,应遵循如下结构:

interface SavedCanvas {
  id: string | number  // 历史记录唯一 ID
  title: string        // 画布重命名标题
  updatedAt: string    // 格式化好的修改日期,例如 "2026-05-29 10:00:23"
  nodes: any[]         // 对应版本的节点数据
  edges: any[]         // 对应版本的连线数据
  viewport: {          // 对应版本的视口配置
    x: number
    y: number
    zoom: number
  }
}

2. 推荐数据库表结构设计 (后端)

若您支持用户保存多份画布,或者希望支持“历史快照版本归档”,推荐设计为以下关联关系:

-- 用户工作流画布主表
CREATE TABLE `user_workflows` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '画布ID',
  `user_id` bigint(20) NOT NULL COMMENT '用户ID',
  `title` varchar(128) DEFAULT '未命名画布' COMMENT '画布当前标题',
  `canvas_data` json DEFAULT NULL COMMENT '当前最新状态的 JSON 数据 (nodes, edges, viewport)',
  `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
  `updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='工作流画布主表';

-- 画布历史版本记录表 (用于提供给 history-list)
CREATE TABLE `workflow_history` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '历史记录ID',
  `workflow_id` bigint(20) NOT NULL COMMENT '关联主表 ID',
  `title` varchar(128) DEFAULT '历史版本' COMMENT '保存时的版本标题',
  `canvas_data` json DEFAULT NULL COMMENT '历史状态数据 (nodes, edges, viewport)',
  `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '保存时间',
  PRIMARY KEY (`id`),
  KEY `idx_workflow` (`workflow_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='画布历史快照表';

💡 4. 集成注意事项与最佳实践

  1. 容器高度问题WorkflowCanvas 必须被包裹在一个具有明确宽度和高度的容器中(例如 width: 100%; height: 100vh;),否则画布的 SVG 视口可能会折叠为 0px 导致组件不可见。
  2. Pinia 共享机制:若您的宿主项目已经实例化了全局 Pinia,画布将自动共享此 Pinia 实例。若您的项目未使用 Pinia,需要在入口文件初始化以避免运行时警告:
    import { createPinia } from 'pinia'
    app.use(createPinia())
  3. Tailwind CSS 兼容性:画布组件内部自带了完备的磨砂玻璃(glassmorphism)与夜间模式样式。若您的项目未使用 Tailwind,无需担心样式冲突,我们的 CSS 打包结果为独立的样式文件。

📡 5. AI 模型与服务对接接口规范

为了使画布的 AI 功能正常运行,集成者需要在 apiConfig 属性中配置后端的 baseUrl、请求头 headers 以及 endpoints 路径。

同时,集成的后端接口必须严格遵循以下定义的数据格式。

0. API 配置注入参数定义 (apiConfig)

interface ApiConfig {
  baseUrl: string                  // 接口请求基地址,如 'https://api.yourdomain.com/v1'
  headers?: Record<string, string> // 自定义请求头(如 Authorization: Bearer token)
  cdnUrl?: string                  // CDN 资源域名(若返回的地址是相对路径,组件会自动拼接此域名前缀)
  endpoints?: {
    models?: string                // 获取模型列表,默认值为 '/models'
    upload?: string                // 文件上传,默认值为 '/upload'
    imageTask?: string             // 创建生图任务,默认值为 '/tasks/image'
    videoTask?: string             // 创建生视频任务,默认值为 '/tasks/video'
    taskPoll?: string              // 任务状态轮询前缀,默认值为 '/tasks'
    chatMessage?: string           // 文本模型流式对话,默认值为 '/chats/messages'
    saveCanvas?: string            // 保存画布,默认值为 '/save'
    listHistory?: string           // 查询历史记录列表,默认值为 '/history_list'
    deleteHistory?: string         // 删除历史记录,默认值为 '/delete_history'
  }
}

1. 获取模型列表 (GET /models)

用于在画布挂载时及节点配置中,渲染可供用户选择的大模型列表。

  • 请求方式GET
  • 响应格式 (Response)
{
  "records": [
    {
      "id": "gpt-4o",
      "name": "GPT-4o",
      "type": "text",
      "status": "active"
    },
    {
      "id": "sdxl-turbo",
      "name": "SDXL Turbo",
      "type": "image",
      "status": "active",
      "supported_aspect_ratios": ["1:1", "16:9", "4:3"]
    },
    {
      "id": "luma-dream-machine",
      "name": "Luma Dream Machine",
      "type": "video",
      "status": "active"
    }
  ]
}
  • 字段说明
    • type:必须是 'text' | 'image' | 'video' 之一,画布会依据此字段将模型归类显示在不同的节点类型中。
    • supported_aspect_ratios:仅对 image / video 模型生效,可选,用于限制该模型支持的生成宽高比例。

2. 文件上传 (POST /upload)

当用户在图片节点或视频节点双击上传本地素材时触发。

  • 请求方式POST
  • 请求格式multipart/form-data
  • 请求体 (Request Body)
    • file:File 二进制对象
  • 响应格式 (Response)
{
  "url": "https://cdn.yourdomain.com/uploads/20260529/abc123xyz.jpg"
}
  • 字段说明
    • 返回的 url 应当是可直接公网访问的图片或视频 file 地址。

3. 大语言模型流式对话 (POST /chats/messages)

在文本节点上点击“运行”时,流式获取 AI 文本回复。

  • 请求方式POST
  • 请求参数 (Query)?model={modelId}
  • 请求体 (Request Body)
{
  "content": "用户当前节点输入的提示词,包含连线至本节点的所有上游文本",
  "image_urls": [
    "data:image/jpeg;base64,xxxx...", 
    "https://cdn.yourdomain.com/image.jpg"
  ],
  "session_id": "xxxx-session-id-xxxx" 
}
  • 请求头 (Headers)
    • Accept: text/event-stream
  • 流式响应规范 (Server-Sent Events): 后端接口需要启用分块流式传输 (text/event-stream),支持返回两种类型的事件数据:
    1. 对话上下文 Session ID 传输(可选,作为流的第一帧或结束帧): 如果需要支持多轮对话上下文,请在响应的 Headers 中附带 X-Chat-Session-ID: <session_id>,或者在流中输出一条 JSON 元数据:
      data: {"session_id": "new-session-id-123"}
    2. 文本内容传输
      data: 欢
      data: 迎
      data: 使用
      data: [DONE]
      data: [DONE] 标识流的正常结束。

4. 创建图片/视频生成任务 (POST /tasks/imagePOST /tasks/video)

当用户在图片或视频生成节点点击“运行”时,由于生成时间长,采用异步任务制提交。

  • 请求方式POST
  • 请求体 (Request Body)
{
  "type": "image",
  "model": "sdxl-turbo",
  "prompt": "生成的提示词内容,包含上游相连的所有文本节点数据",
  "aspect_ratio": "16:9",
  "quality": "standard",
  "image_urls": [
    "data:image/jpeg;base64,xxxx...",
    "https://cdn.yourdomain.com/image.jpg"
  ],
  
  // 以下字段仅在 type = "video" 时传递
  "duration": 5,
  "generate_audio": true,
  "ref_images": [],      // 视频参考图列表
  "head_image": null,    // 视频首帧图(若采用首尾帧模式)
  "tail_image": null     // 视频尾帧图(若采用首尾帧模式)
}
  • 响应格式 (Response)
{
  "id": "task_9876543210"
}
  • 字段说明
    • 请求体中 image_urls / ref_images / head_image 的内容,若用户连线了包含本地 blob 图片的节点,组件会在请求前自动压缩并将其转换为 Base64 格式。

5. 轮询异步任务状态 (GET /tasks/{id})

在任务提交成功并拿到 id 后,画布组件将以 2 秒为间隔对该任务状态进行轮询。

  • 请求方式GET

  • 请求路径/tasks/{id} (例如 /tasks/task_9876543210

  • 响应格式 (Response)

    • 生成中/排队中
      {
        "status": "PROCESSING",
        "progress": 45
      }
    • 执行失败
      {
        "status": "FAILED",
        "error_message": "生图接口调用超时"
      }
    • 执行成功
      {
        "status": "SUCCESS",
        "completed_at": "2026-05-29T10:15:30Z",
        "prompt": "提示词内容",
        "model": "sdxl-turbo",
        "params": {
          "quality": "standard",
          "aspect_ratio": "16:9"
        },
        "result": {
          "videos": [
            {
              "video_url": "/uploads/video_output.mp4",
              "cover_url": "/uploads/video_cover.jpg"
            }
          ],
          "url": "/uploads/image_output.jpg"
        }
      }
  • 状态值及关键字段规范

    • status 可选值为:QUEUED | PROCESSING (进行中),FAILED | FAILURE (失败),SUCCESS (成功)。
    • 若执行成功且返回的 video_urlcover_urlurl 为以 / 开头的相对路径,组件会利用配置的 cdnUrl 自动补全为公网绝对路径,例如 https://cdn.yourdomain.com/uploads/image_output.jpg

6. 保存画布 (POST /save)

当用户在页面上点击“保存”按钮或使用快捷键时触发。如果在 apiConfig 中配置了 baseUrl,组件内部将优先调用该接口。

  • 请求方式POST
  • 请求体 (Request Body)
{
  "id": 123,
  "title": "我的工作流画布",
  "canvas_data": {
    "nodes": [],
    "edges": [],
    "viewport": { "x": 0, "y": 0, "zoom": 1 }
  }
}
  • 响应格式 (Response)
{
  "success": true,
  "id": 12345
}
  • 字段说明
    • id:当前画布的 ID(可选),如果不传则由后端创建并返回新的历史画布 ID。

7. 获取已保存画布的历史记录列表 (GET /history_list)

当打开历史画布面板时调用,用于展现可恢复的快照列表。

  • 请求方式GET
  • 响应格式 (Response)
{
  "records": [
    {
      "id": 123,
      "title": "版本 1",
      "updatedAt": "2026-05-29 10:00:23",
      "canvas_data": {
        "nodes": [],
        "edges": [],
        "viewport": { "x": 0, "y": 0, "zoom": 1 }
      }
    }
  ]
}
  • 说明
    • 组件会自动兼容平铺结构(如 nodes, edges, viewport 直接位于元素最外层)或嵌套结构(嵌套在 canvas_data 属性内)。

8. 删除已保存画布的历史记录 (DELETE /delete_history)

当用户在历史版本弹窗中点击删除快照时触发。

  • 请求方式DELETE
  • 请求路径/delete_history?id={id}
  • 响应格式 (Response)
{
  "success": true
}