打造一个可定制工作流的桌面女友

你是否想过拥有一个可以自定义行为的桌面虚拟角色?本文将带你从零开始,打造一个基于 Live2D 的桌面女友,并赋予她可定制的工作流能力。通过 Electron + LangGraph 的组合,我们可以创建一个既可爱又智能的桌面应用。

通过本项目,你能得到什么:

  1. 一位常驻桌面的虚拟女友:使用 Live2D 技术打造的可爱角色,可以常驻桌面,陪伴你的工作时光
  2. 可定制的工作流系统:通过 LangGraph 构建的工作流引擎,让虚拟角色能够执行各种自定义任务和逻辑
  3. 模板管理系统:可以保存、复用和分享工作流模板,提高工作效率
  4. 高度可扩展的架构:模块化设计,可以轻松添加新的步骤类型、集成 AI 对话、文件操作等功能

项目概览

在开始之前,让我们先了解一下整个项目的架构:

scss 复制代码
┌─────────────────────────────────────────────────────────┐
│                    用户交互层                             │
│  ┌──────────────┐         ┌──────────────┐              │
│  │  Live2D 模型 │         │  工作流管理  │                │
│  │   (桌面女友)  │         │   界面       │               │
│  └──────────────┘         └──────────────┘              │
└─────────────────────────────────────────────────────────┘
                        ↕
┌─────────────────────────────────────────────────────────┐
│                  Electron 应用层                          │
│  ┌──────────────┐         ┌──────────────┐              │
│  │  Express 服务 │         │  BrowserWindow│             │
│  │  (静态资源)   │         │  (工作流UI)  │               │
│  └──────────────┘         └──────────────┘              │
└─────────────────────────────────────────────────────────┘
                        ↕
┌─────────────────────────────────────────────────────────┐
│                 工作流服务层                               │
│  ┌──────────────┐         ┌──────────────┐              │
│  │  FastAPI     │         │  LangGraph   │              │
│  │  (REST API)  │         │  (工作流引擎) │               │
│  └──────────────┘         └──────────────┘              │
└─────────────────────────────────────────────────────────┘

整个系统分为三个层次:

  1. 用户交互层:Live2D 模型交互和工作流管理界面
  2. Electron 应用层:桌面应用框架和资源服务
  3. 工作流服务层:后端 API 和工作流执行引擎

第一步:获取 Live2D 免费模型

Live2D 官方提供了丰富的免费示例模型,我们可以直接下载使用。

访问下载页面

打开 Live2D 官方示例数据集页面:www.live2d.com/zh-CHS/lear...

在这个页面上,你可以看到多个免费模型,包括:

  • Haru:一个可爱的女孩角色
  • Nito:二头身卡通角色

下载模型文件

选择一个你喜欢的模型(推荐从 Haru 开始),下载 FREE 版本。下载后的文件结构通常如下:

bash 复制代码
haru/
├── haru_greeter_t05.moc3          # 模型数据文件
├── haru_greeter_t05.physics3.json # 物理效果配置
├── haru_greeter_t05.pose3.json    # 姿势配置
├── haru_greeter_t05.cdi3.json     # 显示辅助文件
├── index.json                      # 模型索引文件
├── texture_00.png                  # 纹理贴图
├── texture_01.png                  # 纹理贴图
└── motion/                         # 动作文件夹
    ├── haru_g_idle.motion3.json   # 待机动作
    └── ...                         # 其他动作文件

放置模型文件

将下载的模型文件夹放到项目的 electron/live2d/model/ 目录下,例如:

bash 复制代码
electron/
└── live2d/
    └── model/
        └── haru/          # 你下载的模型文件夹
            ├── index.json
            └── ...

第二步:初始化 Electron + Live2D 项目

项目结构

首先,我们需要创建一个 Electron 项目,并集成 Live2D 显示功能。这里使用 live2d-widget github.com/stevenjoezh... 项目的构建工程集成 Live2D。

添加构建工程

将下载 live2d-widget 工程放到以下目录:

markdown 复制代码
electron/
└── live2d/
    └── live2d-widget/

安装依赖

electron 目录下创建 package.json

json 复制代码
{
  "name": "wenko",
  "version": "1.0.0",
  "main": "main.js",
  "scripts": {
    "start": "electron ."
  },
  "dependencies": {
    "electron": "^latest",
    "express": "^4.18.0"
  }
}

然后运行:

bash 复制代码
npm install

创建主窗口

创建 index.html 文件,用于显示 Live2D 模型:

html 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8" />
  <title>Wenko</title>
</head>
<body>
  <div id="wenko_wifu"></div>
  <script src="http://localhost:8080/live2d/live2d-widget/dist/autoload.js"></script>
</body>
</html>

这里我们通过 localhost:8080 来加载 Live2D 的资源文件,这个服务我们会在下一步创建。

live2d-widget 项目和 Live2D 模型有什么关系?

live2d-widget 项目不包括任何模型,仅负责在网页中加载和运行 Live2D 模型。具体逻辑可参考 autoload.js


第三步:配置 Express 静态资源服务

为了让 Live2D 模型文件能够正常加载,我们需要在 Electron 主进程中启动一个 Express 服务器来提供静态文件服务。

修改 main.js

打开 electron/main.js,添加 Express 服务器:

javascript 复制代码
const { app, BrowserWindow } = require('electron');
const path = require('path');
const express = require('express');

// 创建 Express 服务器提供 live2d 静态文件访问
function createStaticServer() {
  const expressApp = express();
  const port = 8080;
  
  // 提供 live2d 目录的静态文件访问
  expressApp.use('/live2d', express.static(path.join(__dirname, 'live2d')));
  
  expressApp.listen(port, () => {
    console.log(`Static server running on http://localhost:${port}`);
  });
}

// 启动静态文件服务器
createStaticServer();

// 创建 Electron 窗口
function createWindow() {
  const mainWindow = new BrowserWindow({
    width: 350,
    height: 400,
    frame: false,
    transparent: true,
    webPreferences: {
      nodeIntegration: false,
      contextIsolation: true,
      webSecurity: false
    }
  });

  mainWindow.loadFile('index.html');
}

app.whenReady().then(() => {
  createWindow();
});

工作流程

bash 复制代码
┌─────────────────┐
│  Electron 启动   │
└────────┬─────────┘
         │
         ├──> 启动 Express 服务器 (端口 8080)
         │    └──> 提供 /live2d/* 静态资源
         │
         └──> 创建 BrowserWindow
              └──> 加载 index.html
                   └──> 通过 http://localhost:8080/live2d/... 加载模型

为什么需要 Express 服务器?

Live2D 的模型文件(.moc3.json.png 等)需要通过 HTTP 协议加载。虽然 Electron 支持 file:// 协议,但某些情况下会遇到跨域问题。使用 Express 提供 HTTP 服务可以:

  1. 避免跨域问题:统一使用 HTTP 协议
  2. 便于调试:可以在浏览器中直接访问资源
  3. 灵活配置:可以添加缓存、压缩等中间件

第四步:构建工作流系统

现在,让我们为桌面女友添加"大脑"------一个可定制的工作流系统。

初始化 LangGraph 项目

在项目根目录下创建 workflow 目录,并初始化 Python 项目:

首先安装 uv(如果还没有安装):

bash 复制代码
curl -LsSf https://astral.sh/uv/install.sh | sh

然后初始化项目

bash 复制代码
mkdir workflow
cd workflow

# 使用 uv 初始化项目
uv init

# 添加依赖
uv add langgraph langgraph-cli fastapi uvicorn httpx pydantic python-dotenv

项目结构

bash 复制代码
workflow/
├── main.py          # FastAPI 应用入口
├── executor.py      # 工作流执行器
├── steps.py         # 步骤定义
├── graph.py         # LangGraph 图定义
├── control_steps.py # 控制流步骤(If/Then/Else)
└── pyproject.toml   # 项目配置

核心组件解析

1. steps.py - 步骤定义

steps.py 定义了所有可用的工作流步骤。每个步骤都是一个类,继承自 Step 基类:

python 复制代码
class Step(ABC):
    """步骤基类"""
    
    def __init__(self, step_type: str, params: Dict[str, Any]):
        self.step_type = step_type
        self.params = params
    
    @abstractmethod
    def execute(self, context: StepContext) -> Any:
        """执行步骤"""
        pass

步骤类型示例

  • SetVar:设置变量

    python 复制代码
    {
      "type": "SetVar",
      "params": {
        "key": "greeting",
        "value": "Hello, World!"
      }
    }
  • GetVar:获取变量

    python 复制代码
    {
      "type": "GetVar",
      "params": {
        "key": "greeting"
      }
    }
  • EchoInput:回显输入

    python 复制代码
    {
      "type": "EchoInput",
      "params": {
        "input_key": "user_input",
        "output_key": "response"
      }
    }

步骤注册表

所有步骤都注册在 STEP_REGISTRY 中,方便动态创建:

python 复制代码
STEP_REGISTRY = {
    'EchoInput': EchoInputStep,
    'SetVar': SetVarStep,
    'GetVar': GetVarStep,
    # ... 更多步骤类型
}

2. executor.py - 工作流执行器

executor.py 负责执行工作流步骤序列:

python 复制代码
class WorkflowExecutor:
    """工作流执行器"""
    
    def execute(self, steps: List[Dict], initial_context: Dict = None):
        # 创建上下文
        context = StepContext(initial_context or {})
        
        # 依次执行每个步骤
        for step_config in steps:
            step_type = step_config.get('type')
            step_params = step_config.get('params', {})
            
            # 从注册表创建步骤实例
            step = create_step(step_type, step_params)
            
            # 执行步骤
            result = step.execute(context)
        
        return {
            'success': True,
            'result': context.variables
        }

执行流程

yaml 复制代码
┌─────────────────┐
│  工作流请求      │
│  {               │
│    steps: [...], │
│    context: {}   │
│  }               │
└────────┬─────────┘
         │
         v
┌─────────────────┐
│  WorkflowExecutor│
└────────┬─────────┘
         │
         v
┌─────────────────┐      ┌──────────────┐
│  StepContext    │<─────│  变量存储     │
│  (上下文)        │      │  variables   │
└────────┬────────┘      └──────────────┘
         │
         v
┌─────────────────────────────────────┐
│  遍历步骤列表                        │
│  ┌──────────┐  ┌──────────┐        │
│  │ Step 1   │→ │ Step 2   │→ ...   │
│  └──────────┘  └──────────┘        │
└─────────────────────────────────────┘
         │
         v
┌─────────────────┐
│  返回执行结果    │
│  {               │
│    success: true,│
│    result: {...} │
│  }               │
└─────────────────┘

3. main.py - REST API 接口

main.py 使用 FastAPI 提供 RESTful API:

核心接口

  1. 执行工作流 - POST /run

    python 复制代码
    @app.post("/run", response_model=WorkflowResponse)
    async def run_workflow(request: WorkflowRequest):
        executor = WorkflowExecutor()
        result = executor.execute(
            steps=request.steps,
            initial_context=request.initial_context or {}
        )
        return result
  2. 模板管理接口

    • POST /templates - 创建模板
    • GET /templates - 列出所有模板
    • GET /templates/{id} - 获取模板详情
    • PUT /templates/{id} - 更新模板
    • DELETE /templates/{id} - 删除模板
    • GET /templates/search/{query} - 搜索模板
    • POST /templates/{id}/execute - 执行模板
  3. 系统接口

    • GET /health - 健康检查
    • GET /steps - 获取步骤注册表

API 调用示例

bash 复制代码
# 执行工作流
curl -X POST http://localhost:8002/run \
  -H "Content-Type: application/json" \
  -d '{
    "steps": [
      {
        "type": "SetVar",
        "params": {"key": "name", "value": "Wenko"}
      },
      {
        "type": "EchoInput",
        "params": {"input_key": "name", "output_key": "greeting"}
      }
    ],
    "initial_context": {}
  }'

启动服务

bash 复制代码
cd workflow
uv run python main.py

服务启动后,访问 http://localhost:8002/docs 可以看到自动生成的 API 文档。


第五步:构建工作流管理界面

最后,我们需要一个友善的界面来管理模板和执行工作流。

修改 main.js 添加工作流窗口

electron/main.js 中添加快捷键监听,打开工作流管理窗口:

javascript 复制代码
ipcMain.on('wenko_shortcut', async (event, data) => {
  // 处理快捷键事件: action: open
  const { action } = data;
  if (action === 'open') {
    const shortcutWindow = new BrowserWindow({
      width: 1200,
      height: 800,
      title: 'Workflow API 测试工具',
      icon: path.join(__dirname, 'assets', 'favicon.ico'),
      frame: true,
      transparent: false,
      alwaysOnTop: false,
      resizable: true,
      hasShadow: true,
      fullscreenable: true,
      skipTaskbar: false,    // 在任务栏显示
      webPreferences: {
        preload: path.join(__dirname, 'preload.js'),
        nodeIntegration: false,
        contextIsolation: true,
        webSecurity: false  // 关闭同源策略和 CSP 检查,方便开发加载任意脚本
      }
    });
    shortcutWindow.loadFile('workflow.html');
  } else {
    console.warn('Unknown action:', action);
  }
});

workflow.html 功能模块

workflow.html 使用 React + Ant Design 构建,包含以下功能:

1. 工作流执行标签页

scss 复制代码
┌─────────────────────────────────────┐
│  工作流执行                          │
├─────────────────────────────────────┤
│  工作流步骤 (JSON)                   │
│  ┌───────────────────────────────┐  │
│  │ [                            │  ││  │   {"type": "SetVar", ...}    │  ││  │ ]                            │  │
│  └───────────────────────────────┘  │
│                                      │
│  初始上下文 (JSON, 可选)              │
│  ┌───────────────────────────────┐  │
│  │ {"key": "value"}              │  │
│  └───────────────────────────────┘  │
│                                      │
│  ☑ 调试模式                          │
│                                      │
│  [执行工作流] [加载示例] [清空]      │
└─────────────────────────────────────┘

2. 模板管理标签页

css 复制代码
┌─────────────────────────────────────┐
│  模板管理                            │
├─────────────────────────────────────┤
│  [+ 创建新模板]                      │
│                                      │
│  ┌───────────────────────────────┐  │
│  │ 模板名称                        │  │
│  │ 描述:这是一个示例模板...        │  │
│  │ 标签:[基础] [示例]              │  │
│  │ [查看] [执行] [编辑] [删除]     │  │
│  └───────────────────────────────┘  │
└─────────────────────────────────────┘

3. 步骤注册表标签页

显示所有可用的步骤类型:

css 复制代码
┌─────────────────────────────────────┐
│  步骤注册表                          │
├─────────────────────────────────────┤
│  [刷新步骤列表]                      │
│                                      │
│  ┌──────┐ ┌──────┐ ┌──────┐        │
│  │EchoInput│ │SetVar│ │GetVar│        │
│  └──────┘ └──────┘ └──────┘        │
│  ┌──────┐ ┌──────┐ ┌──────┐        │
│  │FetchURL│ │ParseJSON│ │...│        │
│  └──────┘ └──────┘ └──────┘        │
└─────────────────────────────────────┘

模板执行流程

当用户点击"执行"按钮时:

bash 复制代码
┌─────────────────┐
│  用户点击执行    │
└────────┬────────┘
         │
         v
┌─────────────────┐
│  弹出对话框      │
│  让用户输入上下文│
└────────┬────────┘
         │
         v
┌─────────────────┐
│  调用 API        │
│  POST /templates/│
│  {id}/execute    │
└────────┬────────┘
         │
         v
┌─────────────────┐
│  显示执行结果    │
└─────────────────┘

完整系统架构

现在,让我们看看整个系统是如何协作的:

javascript 复制代码
┌─────────────────────────────────────────────────────────────┐
│                      用户操作流程                              │
└─────────────────────────────────────────────────────────────┘
                            │
                            v
        ┌───────────────────────────────────┐
        │  1. 启动 Electron 应用              │
        │     - 显示 Live2D 模型             │
        │     - 启动 Express 静态服务         │
        └───────────────┬─────────────────────┘
                        │
                        v
        ┌───────────────────────────────────┐
        │  2. 与 Live2D 模型交互打开管理界面     │
        └───────────────┬─────────────────────┘
                        │
                        v
        ┌───────────────────────────────────┐
        │  3. 在工作流界面中:                │
        │     - 创建/编辑模板                 │
        │     - 执行工作流                    │
        │     - 查看步骤注册表                │
        └───────────────┬─────────────────────┘
                        │
                        v
        ┌───────────────────────────────────┐
        │  4. 前端调用 FastAPI                │
        │     http://localhost:8002/...       │
        └───────────────┬─────────────────────┘
                        │
                        v
        ┌───────────────────────────────────┐
        │  5. FastAPI 处理请求                │
        │     - 解析 JSON                     │
        │     - 调用 WorkflowExecutor         │
        └───────────────┬─────────────────────┘
                        │
                        v
        ┌───────────────────────────────────┐
        │  6. WorkflowExecutor 执行步骤      │
        │     - 创建 StepContext             │
        │     - 遍历步骤列表                 │
        │     - 执行每个步骤                 │
        └───────────────┬─────────────────────┘
                        │
                        v
        ┌───────────────────────────────────┐
        │  7. 返回执行结果                    │
        │     - 成功/失败状态                 │
        │     - 上下文变量                    │
        └───────────────┬─────────────────────┘
                        │
                        v
        ┌───────────────────────────────────┐
        │  8. 前端显示结果                    │
        │     - 成功提示                     │
        │     - 结果 JSON 展示               │
        └───────────────────────────────────┘

使用示例

示例 1:简单的问候工作流

json 复制代码
{
  "steps": [
    {
      "type": "SetVar",
      "params": {
        "key": "name",
        "value": "Wenko"
      }
    },
    {
      "type": "TemplateReplace",
      "params": {
        "template": "你好,{{name}}!今天过得怎么样?",
        "template_key": null,
        "output_key": "greeting"
      }
    }
  ],
  "initial_context": {}
}

执行结果:

json 复制代码
{
  "success": true,
  "result": {
    "name": "Wenko",
    "greeting": "你好,Wenko!今天过得怎么样?"
  }
}

总结

通过本文,我们完成了一个完整的可定制工作流桌面女友系统:

  1. Live2D 模型集成:从官网下载免费模型并集成到 Electron
  2. 静态资源服务:使用 Express 提供模型文件访问
  3. 工作流引擎:基于 LangGraph 构建可扩展的工作流系统
  4. REST API:提供完整的模板管理和执行接口
  5. 管理界面:友好的 Web 界面用于模板管理

这个系统具有很强的扩展性:

  • 可以添加更多步骤类型(如 AI 对话、文件操作等)
  • 可以集成更多 Live2D 模型和动作
  • 可以添加数据库持久化模板
  • 可以实现工作流与 Live2D 模型的联动(根据工作流结果触发模型动作)

希望这篇文章能帮助你打造属于自己的智能桌面女友!🚀


参考资源

相关推荐
●VON4 小时前
Electron 小游戏实战:太空打砖块(Space Breakout)
前端·javascript·electron
一千柯橘4 小时前
Electron 的打包
electron
y***86695 小时前
JavaScript在Node.js中的Electron
javascript·electron·node.js
柒儿吖1 天前
Electron for 鸿蒙PC - Webpack PublicPath 动态设置完整方案
webpack·electron·harmonyos
●VON1 天前
Electron 与鸿蒙 DevEco Studio 的融合实战:从 WebView 到安全 IPC 架构迁移指南
安全·electron
●VON1 天前
Electron 架构解剖:Chromium + Node.js 如何协同工作
架构·electron·node.js
不爱吃糖的程序媛2 天前
鸿蒙PC Electron 打印服务实现详解
华为·electron·harmonyos
一千柯橘2 天前
Electron 第一步
前端·electron
1024小神2 天前
Electron实现多tab页案例,BrowserView/iframe/webview不同方式的区别
前端·javascript·electron