人工智能大模型本地部署AI 应用移动开发AI AgentAI 技能MCP Clients【免费下载链接】galleryA gallery that showcases on-device ML/GenAI use cases and allows people to try and use models locally.项目地址https://gitcode.com/GitHub_Trending/gallery44/gallery点击查看免费下载导读Virtual Piano 是 AI Edge Gallery 仓库中一个基于 JavaScript Webview 的 Agent Skill它允许设备端 LLM Agent 在聊天中直接创建一个可播放、可横向滚动的 88 键虚拟钢琴键盘用户点击琴键即可听到真实钢琴音色。本文将以该 Skill 的README.md、SKILL.md、scripts/与assets/源码为骨架结合 Gallery App 中的run_js工具与 Skill 加载机制完整讲解它的目录结构、触发方式、运行原理、UI/音频实现细节以及在 App 中的使用与调试方法帮助读者既能在 Gallery 中上手体验也能掌握如何复刻一个返回可交互 Webview的 JS Skill。一、Skill 是什么Virtual Piano 在 Gallery 中的定位AI Edge Gallery 是一个展示端侧 ML/GenAI 用例并允许用户在本机尝试模型的应用。由于端侧 LLM 运行在沙箱化的移动环境中无法像云端模型那样随意执行 Python 脚本或 CLI 命令因此 Gallery 提供两类主要执行路径JavaScript Skills在轻量级隐藏 Webview 中运行逻辑作为跨平台的逻辑执行环境原生 App Intent借助 Android/iOS 系统内置能力如发送邮件、短信。Virtual Piano 正是第一类 ——JS Skill的典型范例。它的核心声明见 skills/featured/virtual-piano/SKILL.md--- name: virtual-piano description: Show a virtual piano to play music metadata: homepage: https://github.com/google-ai-edge/gallery/tree/main/skills/featured/virtual-piano --- # Virtual Piano A playable, horizontally-scrolling virtual piano keyboard that uses web audio.从metadata.homepage可以看出这类 Featured Skill 会在 Skill Manager 界面中把技能名渲染为可点击链接。在 skills/README.md 的 Skill Examples 列表中Virtual Piano 被标注为JS Webview类型Badge 分别对应JS-0a9396与Webview-ee9b00这正好对应它脚本运行 交互界面的双层架构。二、目录结构与文件职责Virtual Piano Skill 的完整目录结构如下以仓库根目录为起点skills/featured/virtual-piano/ ├── README.md # Skill 概览、音源来源与 Apache 2.0 许可说明 ├── SKILL.md # Agent 可读的元数据与触发指令 ├── scripts/ │ ├── index.html # 隐藏逻辑执行入口加载 index.js │ └── index.js # 定义 ai_edge_gallery_get_result返回 Webview URL └── assets/ ├── ui.html # 在聊天界面中渲染的可交互钢琴 UI └── assets/ # 88 个按键对应的钢琴 MP3 音色文件1.mp3 ~ 88.mp3对照 skills/README.md 对 JS Skill 的标准结构要求SKILL.md存放于 Skill 目录根部的必需文件包含---包裹的 frontmatter 元数据后接给 LLM 的指令文本scripts/index.html隐藏的headless逻辑执行环境称为逻辑运行器assets/webview.html在聊天 UI 中渲染的交互页面。Virtual Piano 唯一的不同是把交互页面命名为ui.html而非webview.html并在assets/assets/下平铺了 88 个音色文件这与 skills/featured/virtual-piano/assets/ui.html 中以assets/${i}.mp3动态拼接的加载逻辑一一对应。目录名采用 kebab-casevirtual-piano且与SKILL.md中的name字段保持一致这是 Gallery Skill 目录结构的硬性约定见 skills/README.md。三、Agent 如何触发与执行SKILL.md 与 run_js 工具3.1 触发提示词根据 skills/featured/virtual-piano/SKILL.md当用户在聊天中输入以下类型的话术时LLM 会将该 Skill 纳入考虑Open virtual pianoPlay the pianoI want to play pianoShow me a piano keyboard其原理在 skills/README.md 中有说明每个 Skill 的名称与描述会被追加到系统提示system prompt中LLM 根据用户请求与这些元数据做相关性匹配命中后自动调用该 Skill。3.2 执行指令调用 run_js 工具SKILL.md的 Instructions 部分明确要求 LLM 调用run_js工具。该工具的真实签名定义在 RunJsTool.ktTool(description Runs JS script) fun runJs( ToolParam(description The name of skill) skillName: String, ToolParam(description The script name to run. Use index.html if not provided by user) scriptName: String, ToolParam(description The data to pass to the script. Use empty string if not provided by user) data: String, ): MapString, Any即三个参数分别是skillName技能名、scriptName脚本名未指定时默认index.html、data传给脚本的 JSON 字符串未提供时用空串。对照SKILL.md的指令调用 Virtual Piano 时skillNamevirtual-piano、scriptNameindex.html。run_js的执行链路源码位于 RunJsTool.kt大致为通过SkillsProvider.loadSkill(skillName)加载 Skill调用Skill.getJsSkillUrl(scriptName)计算脚本 URL实现见 SkillExtensions.kt——本地导入/内置 Skill 使用LOCAL_URL_BASE/importDirName/scripts/scriptName远程 Skill 使用其skillUrl拼接将CallJsToolAction发送到执行上下文通道等待隐藏 Webview 中脚本执行完成并返回 JSON 字符串用 Moshi 将返回结果解析为CallJsSkillResult含result、image、webview、error等字段——其中webview.url再经Skill.getJsSkillWebviewUrlSkillExtensions.kt解析为assets/下的绝对地址后在聊天中以内联 Webview 渲染。3.3 入口脚本 index.html / index.jsscripts/index.html 本身只有 30 行唯一作用是加载同目录下的index.js!doctype html html langen head meta charsetUTF-8 / meta nameviewport contentwidthdevice-width, initial-scale1.0 / titleVirtual Piano Skill/title /head body !-- This simply runs the script above -- script srcindex.js/script /body /html真正的逻辑在 scripts/index.js它暴露了 JS Skill 必须实现的全局异步接口ai_edge_gallery_get_result挂在window上window[ai_edge_gallery_get_result] async (dataStr) { try { // Points the app directly to your local UI! const fullUrl ui.html?v${Date.now()}; return JSON.stringify({ webview: {url: fullUrl}, result: Success. Tell the user to tap the preview card to play the piano. }); } catch (e) { console.error(e); return JSON.stringify({error: Failed to load piano: ${e.message}}); } };要点返回 Webview 而非文本结果返回值是一个 stringified JSON包含webview: {url: ui.html?v时间戳}与一条result文本。App 端见 RunJsTool.kt会解析出webview字段并将相对 URL 解析为assets/ui.html后以内联 Webview 展示在聊天流中result文本则提示用户点按预览卡片即可弹奏钢琴。URL 加时间戳防缓存ui.html?v${Date.now()}通过查询参数让每次调用都加载最新版本避免 Webview 缓存旧的交互页面。相对路径约定ui.html是相对于assets/目录的路径这是 Gallery 对 Webview URL 的统一约定getJsSkillWebviewUrl会把相对路径拼到baseUrl/assets/下。四、交互界面实现88 键钢琴 ui.html4.1 整体设计assets/ui.html 是一个自包含的单文件交互页面包含样式style、DOM 骨架与内联脚本三部分设计上针对移动端触屏做了专门适配#piano-scroll-container横向滚动容器隐藏滚动条scrollbar-width: none与::-webkit-scrollbar { display: none }并设置touch-action: pan-x以便手指只做横向滑动#piano深色圆角面板包裹键区与底部滚动提示条#scroll-lip显示 ⟷ SLIDE HERE TO SCROLL ⟷页面viewport设置为maximum-scale1.0, user-scalableno禁止缩放以保障触控体验。4.2 琴键生成无偏的 Z-Index 分组脚本首先用循环生成 88 个琴键。实现的关键点在于按白键可选黑键构成一个.key-group固定宽度 60px从而彻底规避黑键因兄弟元素 z-index 被白键遮挡的经典布局 bug源码注释中明确写着 THE FIX: Explicitly lock the wrapper to 60px and remove the z-index bias!const noteNamesArray [C, C#, D, D#, E, F, F#, G, G#, A, A#, B]; while (noteNumber 88) { const group document.createElement(div); group.className key-group; const cOffset (noteNumber 8) % 12; const noteName noteNamesArray[cOffset]; const octave Math.floor((noteNumber 8) / 12); const whiteKey document.createElement(div); whiteKey.className key white; whiteKey.setAttribute(data-note, noteNumber); whiteKey.innerText noteName octave; group.appendChild(whiteKey); if (cOffset ! 4 cOffset ! 11 noteNumber ! 88) { noteNumber; const blackKey document.createElement(div); blackKey.className key black; blackKey.setAttribute(data-note, noteNumber); group.appendChild(blackKey); } keyContainer.appendChild(group); noteNumber; }值得注意的实现细节音名按(noteNumber 8) % 12偏移映射使琴键 1 对应 A 系列音名88 键钢琴的最低音是 A0白键与黑键均携带data-note属性1~88 的 MIDI 键编号作为音频映射键黑键仅在非 E/BcOffset 4/11且非最后一个键时生成符合钢琴黑键排列规律黑键采用position: absolute; right: -15px骑在白键边界上z-index 设为 10白键统一为 1配合 60px 的 group 宽度保证所有黑键稳定浮于白键之上按键按压态:active/.active通过背景变暗与内阴影反馈同时-webkit-tap-highlight-color: transparent去掉移动端默认点击高亮。4.3 音频引擎88 个本地 MP3 音源音频部分为每个键预创建一个Audio对象音源来自assets/目录下 1~88 编号的 MP3 文件const audioNotes {}; for (let i 1; i 88; i) { const audioUrl assets/${i}.mp3; audioNotes[i] new Audio(audioUrl); }播放逻辑绑定在pointerdown/pointerup/pointerleave/pointercancel事件上统一使用 Pointer Events天然覆盖触摸与鼠标key.addEventListener(pointerdown, function (event) { event.stopPropagation(); const note this.getAttribute(data-note); const audio audioNotes[note]; if (audio) { audio.currentTime 0; audio.play().catch((e) console.warn(Waiting for audio:, e)); this.classList.add(active); setTimeout(() { audio.pause(); audio.currentTime 0; }, 2000); } });这里的几个细节值得留意2 秒自动停止每次pointerdown后 2 秒暂停并重置currentTime避免长按或连续点击导致多个音频叠加失控播放失败降级audio.play()返回的 Promise 被.catch捕获并console.warn应对移动端自动播放策略或 Webview 音频策略导致的暂缓播放状态清理pointerup、pointerleave、pointercancel都会移除active类保证按键视觉状态与触控生命周期一致。4.4 自动滚动到中央 C页面加载后 50ms脚本会把键 40即中央 C / middle C滚动到可视区域中央setTimeout(() { const middleC document.querySelector(.key.white[data-note40]); if (middleC) { middleC.scrollIntoView({ behavior: auto, inline: center, block: nearest }); } }, 50);对移动端小屏而言这避免了用户在 88 键中手动寻找中央 C 的尴尬是交互设计上的点睛之笔。五、音源与许可skills/featured/virtual-piano/README.md 明确说明piano sound are from https://github.com/fuhton/piano-mp3 under MIT license即 88 个 MP3 音色来自fuhton/piano-mp3项目遵循 MIT 许可证而 Skill 本体README、脚本、UI遵循 Apache License 2.0。在使用或二次分发该 Skill 时需同时遵守上述两个许可条款。六、在 Gallery App 中体验该 Skill6.1 添加 SkillVirtual Piano 属于 community-featured 列表可按 skills/README.md 中的流程添加在 App 的 Agent Skills 用例中选定模型后点击 Skills chip 进入 Skill Manager点击 () 按钮选择Add skill from featured list在列表中找到 Virtual Piano 并点击即可自动加入系统。对于远程或本地自建版本也可通过Load skill from URLURL 需指向 Skill 文件夹本身可先浏览器访问url/SKILL.md验证或Import local skilladb push virtual-piano/ /sdcard/Download/后用系统文件选择器选中目录两种方式导入。对应的底层实现URL 规范化、SKILL.md 拉取与解析、目录拷贝到内部存储见 SkillManager.kt 与 SkillManager.kt。[!IMPORTANT] 若将 JS Skill 托管在网络上供他人加载务必使用真正的 Web 托管服务如 GitHub Pages、Cloudflare 等。标准 GitHub 仓库 URL 或raw.githubusercontent.com以text/plain返回文件缺少可执行的 MIME 类型会导致 Webview 加载失败见 skills/README.md。若用 GitHub Pages还需在仓库根目录放置空的.nojekyll文件避免 Jekyll 将SKILL.md渲染为 HTML 导致 App 无法解析原始 Markdown。6.2 使用与调试添加后用户只需在聊天中输入如 Open virtual piano 或 Play the pianoAgent 即会调用run_js运行index.html进而在聊天中渲染出可弹奏的钢琴 Webview用户点按预览卡片即可开始演奏。调试方面skills/README.md 的 Tips 部分指出运行 JS Skill 时可展开执行面板查看调用详情与传给脚本的 data同时该面板提供实时 console 日志查看能力——index.js中的console.error与ui.html中的console.warn输出都会反映在其中。七、总结从 Virtual Piano 看 JS Webview Skill 的通用范式Virtual Piano 虽然小巧却完整演示了 Gallery JS Skill 的标准范式可提炼为以下可复用的步骤目录约定kebab-case 目录名 SKILL.mdfrontmatter 含name/description/ 可选metadatascripts/逻辑assets/交互 UI 与资源触发声明在SKILL.mdInstructions 中指示 LLM 调用run_js工具并给出参数入口契约在scripts/index.html暴露window[ai_edge_gallery_get_result]接收data必要时还有secret返回 stringified JSON含result/webview/image/error交互回传在返回的webview.url中使用相对assets/的路径App 会自动将其解析并以内联 Webview 呈现需要传参时可在 URL 后追加查询参数?datavalue交互页用URLSearchParams读取见 skills/README.md 的 Tips资源管理交互页所需静态资源图片、音频等统一放在assets/下通过相对路径引用。理解这套范式后读者不仅能在 Gallery 中畅玩虚拟钢琴更可以据此开发自己的可交互 JS Skill——例如把任何基于 HTML/JS/Web Audio 的创意应用接入端侧 Agent正如仓库中 mood-tracker、interactive-map、text-spinner 等其他 Webview 型 Skill 所做的那样。参考文件索引skills/featured/virtual-piano/README.md — Skill 概览、音源出处与许可证skills/featured/virtual-piano/SKILL.md — 元数据与触发指令skills/featured/virtual-piano/scripts/index.js —ai_edge_gallery_get_result入口实现skills/featured/virtual-piano/scripts/index.html — 隐藏逻辑入口skills/featured/virtual-piano/assets/ui.html — 88 键钢琴交互 UI 与音频引擎skills/README.md — Skill 体系总览JS Skill 标准、Webview/Image/Secret 返回、安装与调试RunJsTool.kt —run_js工具签名与执行流程SkillExtensions.kt — JS Skill URL 与 Webview URL 解析规则SkillManager.kt — Skill 的 URL/本地导入与校验实现赞分享人工智能大模型本地部署AI 应用移动开发AI AgentAI 技能MCP Clients【免费下载链接】galleryA gallery that showcases on-device ML/GenAI use cases and allows people to try and use models locally.项目地址https://gitcode.com/GitHub_Trending/gallery44/gallery点击查看免费下载相关推荐AI Edge Gallery Virtual Piano 技能剖析从 SKILL.md 定义到 Web Audio 钢琴实现AI Edge Gallery Virtual Piano 技能剖析从 SKILL.md 定义到 Web Audio 钢琴实现 Virtual Piano 是人工智能大模型本地部署AI 应用移动开发AI AgentAI 技能MCP ClientsAI Edge Gallery 交互式地图技能 interactive-map 全解析从 SKILL.md 指令到 WebView 渲染的端侧 Agent 调用链AI Edge Gallery 交互式地图技能 interactive map 全解析从 SKILL.md 指令到 WebView 渲染的端侧 Agent 调人工智能大模型本地部署AI 应用移动开发AI AgentAI 技能MCP ClientsAI Edge Gallery 定时通知调度实战解析 Schedule Notification Skill 与 Android 端实现AI Edge Gallery 定时通知调度实战解析 Schedule Notification Skill 与 Android 端实现 本指南以 AI Ed人工智能大模型本地部署AI 应用移动开发AI AgentAI 技能MCP Clients上一篇Presto C 开发实战使用 CLion Dev Containers 在 Docker 中开发与调试 Prestissimo下一篇Execa Streams 完全指南从子进程 IO 到 Node.js 与 Web 流创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考