Lite-Avatar插件开发VSCode扩展实战如果你正在用Lite-Avatar做数字人开发每次都要在终端里敲一堆命令是不是觉得有点麻烦想象一下如果能在VSCode里直接管理你的数字人形象、一键启动对话、实时查看效果那开发体验得多顺畅。今天我就带你从零开始手把手开发一个专为Lite-Avatar设计的VSCode插件。这个插件能帮你把那些繁琐的部署、配置、运行流程都集成到编辑器里让你专注在数字人本身的开发上。1. 环境准备与项目初始化1.1 安装必备工具首先确保你的电脑上已经装好了这些基础工具# 检查Node.js版本需要16.x或更高 node --version # 检查npm版本 npm --version # 安装Yeoman和VSCode扩展生成器 npm install -g yo generator-code如果你还没装Node.js可以去官网下载LTS版本安装过程很简单一路下一步就行。1.2 创建插件项目打开终端找个你喜欢的位置运行下面的命令来生成插件骨架# 创建项目目录 mkdir lite-avatar-extension cd lite-avatar-extension # 使用Yeoman生成器 yo code运行yo code之后你会看到一些选项需要选择选择扩展类型选New Extension (TypeScript)这样能用TypeScript写类型检查更友好输入扩展名填lite-avatar-helper输入标识符用默认的lite-avatar-helper就行输入描述写VSCode extension for Lite-Avatar development是否初始化Git仓库选Yes方便后续版本管理选择包管理器选npm就行等它跑完你的项目目录结构大概长这样lite-avatar-extension/ ├── .vscode/ # VSCode配置 ├── src/ │ ├── extension.ts # 插件主入口 │ └── test/ # 测试文件 ├── package.json # 插件配置 ├── tsconfig.json # TypeScript配置 ├── .gitignore └── README.md1.3 安装依赖并测试进入项目目录安装依赖并试运行一下# 安装依赖 npm install # 编译TypeScript npm run compile # 启动调试模式 # 按F5或者点VSCode的运行和调试面板里的启动按钮如果一切正常会弹出一个新的VSCode窗口标题栏写着[扩展开发主机]这说明你的插件环境已经搭好了。2. 插件架构设计2.1 理解Lite-Avatar的工作流在开始写代码之前我们先想想Lite-Avatar的典型使用场景配置管理需要编辑YAML配置文件设置API密钥、选择数字人形象模型下载从ModelScope或HuggingFace下载预训练模型服务启动运行uv run src/demo.py --config xxx.yaml状态监控查看服务日志、监控显存占用形象管理浏览、选择、预览不同的数字人形象我们的插件就是要围绕这些核心场景来设计功能。2.2 设计插件模块结构我建议把插件分成这几个模块每个模块负责一块功能// 这是我们的模块结构规划不是实际代码 src/ ├── extension.ts # 插件入口注册所有功能 ├── config/ # 配置管理模块 │ ├── ConfigManager.ts │ └── templates/ # 配置文件模板 ├── commands/ # 命令模块 │ ├── StartService.ts │ ├── StopService.ts │ ├── DownloadModel.ts │ └── SelectAvatar.ts ├── views/ # 视图模块 │ ├── AvatarGallery.ts │ ├── ServiceStatus.ts │ └── LogViewer.ts ├── utils/ # 工具函数 │ ├── terminal.ts │ ├── fileSystem.ts │ └── notification.ts └── types/ # 类型定义 └── index.ts这样分层的好处是代码清晰每个模块职责明确以后加新功能或者改现有功能都很方便。3. 核心功能实现3.1 配置管理功能先来实现配置管理这是最基础也是最重要的功能。我们创建一个ConfigManager类// src/config/ConfigManager.ts import * as vscode from vscode; import * as fs from fs; import * as path from path; import * as yaml from js-yaml; export interface LiteAvatarConfig { service: { host: string; port: number; cert_file?: string; cert_key?: string; }; chat_engine: { model_root: string; handler_configs: Recordstring, any; }; } export class ConfigManager { private configPath: string; constructor(private context: vscode.ExtensionContext) { // 默认配置路径用户主目录下的.lite-avatar目录 const homeDir process.env.HOME || process.env.USERPROFILE; this.configPath path.join(homeDir!, .lite-avatar, config); } // 创建默认配置文件 async createDefaultConfig(): Promisestring { const defaultConfig: LiteAvatarConfig { service: { host: 0.0.0.0, port: 8282 }, chat_engine: { model_root: ./models, handler_configs: { LiteAvatar: { module: avatar/liteavatar/avatar_handler_liteavatar, avatar_name: 20250408/sample_data, fps: 25, use_gpu: true }, LLM_Bailian: { model_name: qwen-plus, api_key: your-api-key-here } } } }; // 确保目录存在 await fs.promises.mkdir(path.dirname(this.configPath), { recursive: true }); // 写入YAML文件 const yamlContent yaml.dump(defaultConfig); await fs.promises.writeFile(this.configPath, yamlContent, utf8); return this.configPath; } // 读取配置文件 async loadConfig(configPath?: string): PromiseLiteAvatarConfig { const filePath configPath || this.configPath; if (!fs.existsSync(filePath)) { throw new Error(配置文件不存在: ${filePath}); } const content await fs.promises.readFile(filePath, utf8); return yaml.load(content) as LiteAvatarConfig; } // 保存配置文件 async saveConfig(config: LiteAvatarConfig, configPath?: string): Promisevoid { const filePath configPath || this.configPath; const yamlContent yaml.dump(config); await fs.promises.writeFile(filePath, yamlContent, utf8); } // 打开配置文件编辑器 async openConfigEditor(): Promisevoid { const configFile vscode.Uri.file(this.configPath); // 如果文件不存在先创建默认配置 if (!fs.existsSync(this.configPath)) { await this.createDefaultConfig(); } // 在编辑器中打开 const document await vscode.workspace.openTextDocument(configFile); await vscode.window.showTextDocument(document); // 添加语法高亮如果安装了YAML插件 await vscode.languages.setTextDocumentLanguage(document, yaml); } }然后在插件入口注册配置管理命令// src/extension.ts import * as vscode from vscode; import { ConfigManager } from ./config/ConfigManager; export function activate(context: vscode.ExtensionContext) { const configManager new ConfigManager(context); // 注册打开配置文件命令 const openConfigCommand vscode.commands.registerCommand( lite-avatar.openConfig, async () { try { await configManager.openConfigEditor(); vscode.window.showInformationMessage(配置文件已打开); } catch (error) { vscode.window.showErrorMessage(打开配置文件失败: ${error}); } } ); // 注册创建默认配置命令 const createConfigCommand vscode.commands.registerCommand( lite-avatar.createDefaultConfig, async () { try { const configPath await configManager.createDefaultConfig(); vscode.window.showInformationMessage(默认配置文件已创建: ${configPath}); // 询问是否要打开编辑 const open await vscode.window.showInformationMessage( 配置文件已创建是否要打开编辑, 打开, 稍后 ); if (open 打开) { await configManager.openConfigEditor(); } } catch (error) { vscode.window.showErrorMessage(创建配置文件失败: ${error}); } } ); context.subscriptions.push(openConfigCommand, createConfigCommand); }3.2 服务启动与停止接下来实现服务的启动和停止功能。这里的关键是要在VSCode的终端里运行命令// src/commands/StartService.ts import * as vscode from vscode; import * as path from path; import { ConfigManager } from ../config/ConfigManager; export class StartServiceCommand { private terminal: vscode.Terminal | undefined; constructor( private configManager: ConfigManager, private context: vscode.ExtensionContext ) {} async execute(configPath?: string): Promisevoid { try { // 检查配置文件 const config await this.configManager.loadConfig(configPath); // 获取项目根目录假设用户已经在OpenAvatarChat目录下 const workspaceFolders vscode.workspace.workspaceFolders; if (!workspaceFolders || workspaceFolders.length 0) { throw new Error(请先打开OpenAvatarChat项目目录); } const projectRoot workspaceFolders[0].uri.fsPath; const configFile configPath || path.join(projectRoot, config, chat_with_openai_compatible_bailian_cosyvoice.yaml); // 创建或获取终端 if (!this.terminal || this.terminal.exitStatus) { this.terminal vscode.window.createTerminal({ name: Lite-Avatar Service, cwd: projectRoot }); } // 显示终端 this.terminal.show(); // 构建启动命令 const command uv run src/demo.py --config ${configFile}; // 在终端中执行 this.terminal.sendText(command); // 保存终端引用方便后续操作 this.context.workspaceState.update(liteAvatarTerminal, this.terminal); vscode.window.showInformationMessage(Lite-Avatar服务正在启动...); } catch (error) { vscode.window.showErrorMessage(启动服务失败: ${error}); } } // 停止服务 async stop(): Promisevoid { if (this.terminal) { // 发送CtrlC中断命令 this.terminal.sendText(\x03); this.terminal.dispose(); this.terminal undefined; vscode.window.showInformationMessage(Lite-Avatar服务已停止); } } }在插件入口注册这些命令// src/extension.ts 继续添加 import { StartServiceCommand } from ./commands/StartService; export function activate(context: vscode.ExtensionContext) { // ... 之前的代码 const configManager new ConfigManager(context); const startServiceCommand new StartServiceCommand(configManager, context); // 注册启动服务命令 const startCommand vscode.commands.registerCommand( lite-avatar.startService, async () { await startServiceCommand.execute(); } ); // 注册停止服务命令 const stopCommand vscode.commands.registerCommand( lite-avatar.stopService, async () { await startServiceCommand.stop(); } ); context.subscriptions.push(startCommand, stopCommand); }3.3 数字人形象管理数字人形象管理是Lite-Avatar开发中的重要环节。我们来实现一个形象画廊视图// src/views/AvatarGallery.ts import * as vscode from vscode; import * as fs from fs; import * as path from path; export interface AvatarInfo { id: string; name: string; description: string; previewImage?: string; configPath: string; } export class AvatarGalleryProvider implements vscode.TreeDataProviderAvatarItem { private _onDidChangeTreeData new vscode.EventEmitterAvatarItem | undefined(); readonly onDidChangeTreeData this._onDidChangeTreeData.event; private avatars: AvatarInfo[] []; constructor(private context: vscode.ExtensionContext) { this.loadAvatars(); } refresh(): void { this.loadAvatars(); this._onDidChangeTreeData.fire(undefined); } private async loadAvatars(): Promisevoid { // 这里可以硬编码一些预置形象或者从远程API获取 this.avatars [ { id: sample_data, name: 默认形象, description: Lite-Avatar自带的示例形象, configPath: 20250408/sample_data }, { id: business_woman, name: 商务女性, description: 适合企业演示的专业形象, configPath: 20250408/business_woman }, { id: casual_man, name: 休闲男性, description: 日常对话的轻松形象, configPath: 20250408/casual_man } ]; // 也可以扫描本地目录 const workspaceFolders vscode.workspace.workspaceFolders; if (workspaceFolders) { const avatarDir path.join(workspaceFolders[0].uri.fsPath, resource, avatar, liteavatar); if (fs.existsSync(avatarDir)) { const subDirs await fs.promises.readdir(avatarDir, { withFileTypes: true }); for (const dir of subDirs) { if (dir.isDirectory()) { const avatarId path.basename(dir.name); this.avatars.push({ id: avatarId, name: this.formatAvatarName(avatarId), description: 本地形象: ${avatarId}, configPath: avatarId }); } } } } } private formatAvatarName(id: string): string { // 简单的格式化把下划线变成空格首字母大写 return id .split(_) .map(word word.charAt(0).toUpperCase() word.slice(1)) .join( ); } getTreeItem(element: AvatarItem): vscode.TreeItem { return element; } getChildren(element?: AvatarItem): ThenableAvatarItem[] { if (element) { // 如果有子节点可以在这里返回 return Promise.resolve([]); } else { return Promise.resolve( this.avatars.map(avatar new AvatarItem( avatar, vscode.TreeItemCollapsibleState.None, { command: lite-avatar.selectAvatar, title: 选择形象, arguments: [avatar] } )) ); } } } class AvatarItem extends vscode.TreeItem { constructor( public readonly avatar: AvatarInfo, public readonly collapsibleState: vscode.TreeItemCollapsibleState, public readonly command?: vscode.Command ) { super(avatar.name, collapsibleState); this.tooltip avatar.description; this.description avatar.description; // 设置图标 this.iconPath new vscode.ThemeIcon(person); } }然后在插件入口注册这个视图// src/extension.ts 继续添加 import { AvatarGalleryProvider } from ./views/AvatarGallery; export function activate(context: vscode.ExtensionContext) { // ... 之前的代码 // 注册形象画廊视图 const avatarProvider new AvatarGalleryProvider(context); const treeView vscode.window.createTreeView(liteAvatarAvatars, { treeDataProvider: avatarProvider, showCollapseAll: true }); // 注册选择形象命令 const selectAvatarCommand vscode.commands.registerCommand( lite-avatar.selectAvatar, async (avatar) { // 这里可以实现更新配置文件中的avatar_name const config await configManager.loadConfig(); // 更新LiteAvatar配置 if (config.chat_engine.handler_configs.LiteAvatar) { config.chat_engine.handler_configs.LiteAvatar.avatar_name avatar.configPath; } await configManager.saveConfig(config); vscode.window.showInformationMessage(已选择形象: ${avatar.name}); // 询问是否要重启服务 const restart await vscode.window.showInformationMessage( 形象已更新是否要重启服务, 重启, 稍后 ); if (restart 重启) { await startServiceCommand.stop(); await startServiceCommand.execute(); } } ); // 注册刷新命令 const refreshAvatarsCommand vscode.commands.registerCommand( lite-avatar.refreshAvatars, () { avatarProvider.refresh(); } ); context.subscriptions.push( treeView, selectAvatarCommand, refreshAvatarsCommand ); }3.4 状态监控面板最后我们来实现一个状态监控面板实时显示服务运行状态// src/views/ServiceStatus.ts import * as vscode from vscode; export class ServiceStatusProvider implements vscode.WebviewViewProvider { public static readonly viewType liteAvatarServiceStatus; private _view?: vscode.WebviewView; constructor(private context: vscode.ExtensionContext) {} resolveWebviewView( webviewView: vscode.WebviewView, context: vscode.WebviewViewResolveContext, token: vscode.CancellationToken ): void | Thenablevoid { this._view webviewView; webviewView.webview.options { enableScripts: true, localResourceRoots: [this.context.extensionUri] }; webviewView.webview.html this.getHtml(); // 定期更新状态 this.startStatusUpdates(); } private getHtml(): string { return !DOCTYPE html html langen head meta charsetUTF-8 meta nameviewport contentwidthdevice-width, initial-scale1.0 titleLite-Avatar 服务状态/title style body { padding: 10px; font-family: var(--vscode-font-family); color: var(--vscode-foreground); } .status-item { margin: 10px 0; padding: 8px; border-radius: 4px; background: var(--vscode-editor-background); } .status-label { font-weight: bold; margin-bottom: 4px; } .status-value { font-family: monospace; } .status-running { color: #4CAF50; } .status-stopped { color: #F44336; } .status-warning { color: #FF9800; } button { margin-top: 10px; padding: 6px 12px; background: var(--vscode-button-background); color: var(--vscode-button-foreground); border: none; border-radius: 2px; cursor: pointer; } button:hover { background: var(--vscode-button-hoverBackground); } /style /head body h3服务状态监控/h3 div classstatus-item div classstatus-label服务状态/div div idserviceStatus classstatus-value status-stopped已停止/div /div div classstatus-item div classstatus-label运行时间/div div iduptime classstatus-value--/div /div div classstatus-item div classstatus-label内存占用/div div idmemoryUsage classstatus-value--/div /div div classstatus-item div classstatus-label显存占用/div div idgpuMemory classstatus-value--/div /div div classstatus-item div classstatus-label活动连接/div div idactiveConnections classstatus-value0/div /div button onclickrefreshStatus()刷新状态/button script let statusInterval; function updateStatus(data) { document.getElementById(serviceStatus).textContent data.status; document.getElementById(serviceStatus).className status-value (data.status 运行中 ? status-running : status-stopped); document.getElementById(uptime).textContent data.uptime || --; document.getElementById(memoryUsage).textContent data.memory || --; document.getElementById(gpuMemory).textContent data.gpuMemory || --; document.getElementById(activeConnections).textContent data.connections || 0; } function refreshStatus() { // 这里可以通过Webview API与扩展通信获取真实状态 // 暂时用模拟数据 updateStatus({ status: 运行中, uptime: 2小时15分, memory: 1.2 GB, gpuMemory: 3.8 GB / 8 GB, connections: 1 }); } // 初始加载 refreshStatus(); // 每10秒自动刷新 statusInterval setInterval(refreshStatus, 10000); // 清理定时器 window.addEventListener(message, event { if (event.data.command dispose) { clearInterval(statusInterval); } }); /script /body /html ; } private startStatusUpdates(): void { // 这里可以实现定期从服务获取状态信息 // 暂时用定时器模拟 setInterval(() { if (this._view) { this._view.webview.postMessage({ command: updateStatus, data: { status: 运行中, timestamp: new Date().toLocaleTimeString() } }); } }, 5000); } dispose(): void { if (this._view) { this._view.webview.postMessage({ command: dispose }); } } }注册状态面板// src/extension.ts 最后部分 import { ServiceStatusProvider } from ./views/ServiceStatus; export function activate(context: vscode.ExtensionContext) { // ... 之前的代码 // 注册状态面板 const statusProvider new ServiceStatusProvider(context); context.subscriptions.push( vscode.window.registerWebviewViewProvider( ServiceStatusProvider.viewType, statusProvider ) ); // 所有命令注册完成后 console.log(Lite-Avatar插件已激活); } export function deactivate() { // 清理资源 console.log(Lite-Avatar插件已停用); }4. 插件打包与发布4.1 完善package.json配置打开package.json我们需要完善一些配置{ name: lite-avatar-helper, displayName: Lite-Avatar Helper, description: VSCode extension for Lite-Avatar development, version: 0.1.0, publisher: your-name, engines: { vscode: ^1.60.0 }, categories: [ Other ], activationEvents: [ onCommand:lite-avatar.openConfig, onCommand:lite-avatar.startService, onView:liteAvatarAvatars, onView:liteAvatarServiceStatus ], main: ./out/extension.js, contributes: { commands: [ { command: lite-avatar.openConfig, title: Lite-Avatar: 打开配置文件 }, { command: lite-avatar.createDefaultConfig, title: Lite-Avatar: 创建默认配置 }, { command: lite-avatar.startService, title: Lite-Avatar: 启动服务 }, { command: lite-avatar.stopService, title: Lite-Avatar: 停止服务 }, { command: lite-avatar.selectAvatar, title: Lite-Avatar: 选择数字人形象 }, { command: lite-avatar.refreshAvatars, title: Lite-Avatar: 刷新形象列表 } ], views: { explorer: [ { id: liteAvatarAvatars, name: 数字人形象 } ], activitybar: [ { id: liteAvatarServiceStatus, name: 服务状态, type: webview } ] }, menus: { view/title: [ { command: lite-avatar.refreshAvatars, when: view liteAvatarAvatars, group: navigation } ], view/item/context: [ { command: lite-avatar.selectAvatar, when: view liteAvatarAvatars, group: inline } ] } }, scripts: { vscode:prepublish: npm run compile, compile: tsc -p ./, watch: tsc -watch -p ./, pretest: npm run compile npm run lint, lint: eslint src --ext ts, package: vsce package }, devDependencies: { types/vscode: ^1.60.0, types/node: 16.x, typescript-eslint/eslint-plugin: ^5.59.0, typescript-eslint/parser: ^5.59.0, eslint: ^8.0.0, typescript: ^4.9.0 }, dependencies: { js-yaml: ^4.1.0 } }4.2 打包插件安装VS Code扩展打包工具并打包# 安装vsce npm install -g vscode/vsce # 打包插件 vsce package这会生成一个.vsix文件比如lite-avatar-helper-0.1.0.vsix。4.3 本地安装测试你可以在本地安装这个插件进行测试# 在VSCode中安装 code --install-extension lite-avatar-helper-0.1.0.vsix或者直接在VSCode的扩展面板中点击...菜单选择从VSIX安装。4.4 发布到市场可选如果你想分享给其他人使用可以发布到VSCode市场注册Azure DevOps账号创建发布者账号获取Personal Access Token发布插件vsce publish -p 你的token5. 使用插件开发Lite-Avatar项目5.1 完整工作流演示现在让我们看看用这个插件开发Lite-Avatar项目的完整流程打开OpenAvatarChat项目code /path/to/OpenAvatarChat初始化配置按CtrlShiftP打开命令面板输入Lite-Avatar: 创建默认配置按照提示填写API密钥等信息选择数字人形象在侧边栏找到数字人形象视图浏览可用的形象点击选择喜欢的形象启动服务按CtrlShiftP输入Lite-Avatar: 启动服务观察终端输出等待服务启动完成监控状态点击活动栏的服务状态图标查看实时运行状态监控资源使用情况测试对话打开浏览器访问http://localhost:8282与数字人进行对话测试观察服务状态面板的变化5.2 常见问题处理插件还提供了一些调试和问题处理功能// 可以添加的调试命令 const debugCommands [ { command: lite-avatar.checkDependencies, title: 检查依赖, handler: async () { // 检查Python、uv、CUDA等依赖 } }, { command: lite-avatar.viewLogs, title: 查看日志, handler: async () { // 打开日志文件或显示最近日志 } }, { command: lite-avatar.killProcess, title: 强制结束进程, handler: async () { // 强制结束所有相关进程 } } ];6. 总结开发这个Lite-Avatar插件的过程其实就是一个典型的VSCode扩展开发实战。我们从环境搭建开始一步步实现了配置管理、服务控制、形象选择、状态监控等核心功能。用下来感觉这种集成开发环境的方式确实能大大提升开发效率。以前需要在终端、编辑器、浏览器之间来回切换现在大部分操作都能在VSCode里完成。特别是对于刚接触Lite-Avatar的开发者来说能减少很多配置上的困扰。当然这个插件还有很多可以完善的地方。比如可以加入模型下载进度显示、支持更多的配置模板、添加性能分析工具等等。如果你有兴趣完全可以基于这个基础继续扩展。插件开发本身不算太难关键是要理解VSCode的扩展模型和API设计。一旦掌握了基本模式实现各种功能就是水到渠成的事了。希望这个教程能帮你打开VSCode插件开发的大门也让你在Lite-Avatar开发中更加得心应手。获取更多AI镜像想探索更多AI镜像和应用场景访问 CSDN星图镜像广场提供丰富的预置镜像覆盖大模型推理、图像生成、视频生成、模型微调等多个领域支持一键部署。