Skills Plugins MCP Prompt Model 博客 我的中心

ui-demo

使用 Playwright 录制精美的 UI 演示视频。当用户要求创建 Web 应用的演示、导览、屏幕录制或教程视频时使用。生成带有可见光标、自然节奏和专业感的 WebM 视频。

DeepseekModel Curated skill Quality Excellent · 90 v1.0.0

Get

https://deepseekmodel.com/api/download.php?id=affaan-m-ecc-docs-zh-cn-skills-ui-demo-skill-md&format=skill
Download .skill Standard format with system_prompt and model_config, ready for any agent framework
The actual content of the system_prompt field in the .skill file.
name ui-demo description 使用 Playwright 录制精美的 UI 演示视频。当用户要求创建 Web 应用的演示、导览、屏幕录制或教程视频时使用。生成带有可见光标、自然节奏和专业感的 WebM 视频。 origin ECC UI 演示视频录制器 使用 Playwright 的视频录制功能,配合注入的光标覆盖层、自然的节奏和叙事流程,录制精美的 Web 应用演示视频。 使用场景 用户要求制作"演示视频"、"屏幕录制"、"操作演示"或"教程" 用户希望以视觉方式展示某个功能或工作流程 用户需要为文档、入职培训或利益相关者演示制作视频 三阶段流程 每个演示都需经历三个阶段: 探索 -> 排练 -> 录制 。切勿直接跳至录制阶段。 阶段 1:探索 在编写任何脚本之前,先探索目标页面,了解实际内容。 原因 你无法为未见过的内容编写脚本。字段可能是 <input> 而非 <textarea> ,下拉菜单可能是自定义组件而非 <select> ,评论框可能支持 @mentions 或 #tags 。假设会无声地破坏录制。 方法 导航至流程中的每个页面,并转储其交互元素: // Run this for each page in the flow BEFORE writing the demo script const fields = await page. evaluate ( () => { const els = []; document . querySelectorAll ( 'input, select, textarea, button, [contenteditable]' ). forEach ( el => { if (el. offsetParent !== null ) { els. push ({ tag : el. tagName , type : el. type || '' , name : el. name || '' , placeholder : el. placeholder || '' , text : el. textContent ?. trim (). substring ( 0 , 40 ) || '' , contentEditable : el. contentEditable === 'true' , role : el. getAttribute ( 'role' ) || '' , }); } }); return els; }); console . log ( JSON . stringify (fields, null , 2 )); 需要关注的内容 表单字段 :它们是 <select> 、 <input> 、自定义下拉菜单还是组合框? 选择选项 :转储选项的值和文本。占位符通常包含 value="0" 或 value="" ,看起来非空。使用 Array.from(el.options).map(o => ({ value: o.value, text: o.text })) 。跳过文本包含"选择"或值为 "0" 的选项。 富文本 :评论框是否支持 @mentions 、 #tags 、Markdown 或表情符号?检查占位符文本。 必填字段 :哪些字段会阻止表单提交?检查标签中的 required 、 * ,并尝试提交空表单以查看验证错误。 动态内容 :字段是否在填写其他字段后出现? 按钮标签 :确切的文本,如 "Submit" 、 "Submit Request" 或 "Send" 。 表格列标题 :对于表格驱动的模态框,将每个 input[type="number"] 映射到其列标题,而不是假设所有数字输入都表示相同含义。 输出 每个页面的字段映射,用于在脚本中编写正确的选择器。示例: /purchase-requests/new: - 预算代码: <select> (页面上的第一个下拉框,4个选项) - 期望交付日期: <input type="date"> - 背景说明: <textarea> (非输入框) - BOM表: 可内联编辑的单元格,包含 span.cursor-pointer -> input 模式 - 提交: <button> 文本="提交" /purchase-requests/N (详情): - 评论: <input placeholder="输入消息..."> 支持 @用户 和 #PR 标签 - 发送: <button> 文本="发送" (在输入内容前处于禁用状态) 阶段 2:排练 在不录制的情况下运行所有步骤。验证每个选择器都能解析。 原因 静默的选择器失败是演示录制中断的主要原因。排练可以在浪费录制之前发现它们。 方法 使用 ensureVisible ,一个记录日志并大声报错的包装器: async function ensureVisible ( page, locator, label ) { const el = typeof locator === 'string' ? page. locator (locator). first () : locator; const visible = await el. isVisible (). catch ( () => false ); if (!visible) { const msg = `REHEARSAL FAIL: " ${label} " not found - selector: ${ typeof locator === 'string' ? locator : '(locator object)' } ` ; console . error (msg); const found = await page. evaluate ( () => { return Array . from ( document . querySelectorAll ( 'button, input, select, textarea, a' )) . filter ( el => el. offsetParent !== null ) . map ( el => ` ${el.tagName} [ ${el.type || '' } ] " ${el.textContent?.trim().substring( 0 , 30 )} "` ) . join ( '\n ' ); }); console . error ( ' Visible elements:\n ' + found); return false ; } console . log ( `REHEARSAL OK: " ${label} "` ); return true ; } 排练脚本结构 const steps = [ { label : 'Login email field' , selector : '#email' }, { label : 'Login submit' , selector : 'button[type="submit"]' }, { label : 'New Request button' , selector : 'button:has-text("New Request")' }, { label : 'Budget Code select' , selector : 'select' }, { label : 'Delivery date' , selector : 'input[type="date"]:visible' }, { label : 'Description field' , selector : 'textarea:visible' }, { label : 'Add Item button' , selector : 'button:has-text("Add Item")' }, { label : 'Submit button' , selector : 'button:has-text("Submit")' }, ]; let allOk = true ; for ( const step of steps) { if (! await ensureVisible (page, step. selector , step. label )) { allOk = false ; } } if (!allOk) { console . error ( 'REHEARSAL FAILED - fix selectors before recording' ); process. exit ( 1 ); } console . log ( 'REHEARSAL PASSED - all selectors verified' ); 排练失败时 读取可见元素转储。 找到正确的选择器。 更新脚本。 重新运行排练。 仅在所有选择器通过后才继续。 阶段 3:录制 仅在探索和排练通过后,才创建录制。 录制原则 1. 叙事流程 将视频规划为一个故事。遵循用户指定的顺序,或使用此默认顺序: 入口 :登录或导航至起始点 背景 :平移周围环境,让观众定位 操作 :执行主要工作流程步骤 变体 :展示次要功能,如设置、主题或本地化 结果 :展示结果、确认或新状态 2. 节奏 登录后: 4s 导航后: 3s 点击按钮后: 2s 主要步骤之间: 1.5-2s 最终操作后: 3s 输入延迟:每个字符 25-40ms 3. 光标覆盖层 注入一个跟随鼠标移动的 SVG 箭头光标: async function injectCursor ( page ) { await page. evaluate ( () => { if ( document . getElementById ( 'demo-cursor' )) return ; const cursor = document . createElement ( 'div' ); cursor. id = 'demo-cursor' ; cursor. innerHTML = `<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> <path d="M5 3L19 12L12 13L9 20L5 3Z" fill="white" stroke="black" stroke-width="1.5" stroke-linejoin="round"/> </svg>` ; cursor. style . cssText = ` position: fixed; z-index: 999999; pointer-events: none; width: 24px; height: 24px; transition: left 0.1s, top 0.1s; filter: drop-shadow(1px 1px 2px rgba(0,0,0,0.3)); ` ; cursor. style . left = '0px' ; cursor. style . top = '0px' ; document . body . appendChild (cursor); document . addEventListener ( 'mousemove' , ( e ) => { cursor. style . left = e. clientX + 'px' ; cursor. style . top = e. clientY + 'px' ; }); }); } 每次页面导航后调用 injectCursor(page) ,因为覆盖层会在导航时被销毁。 4. 鼠标移动 切勿瞬移光标。在点击前移动到目标: async function moveAndClick ( page, locator, label, opts = {} ) { const { postClickDelay = 800 , ...clickOpts } = opts; const el = typeof locator === 'string' ? page. locator (locator). first () : locator; const visible = await el. isVisible (). catch ( () => false ); if (!visible) { console . error ( `WARNING: moveAndClick skipped - " ${label} " not visible` ); return false ; } try { await el. scrollIntoViewIfNeeded (); await page. waitForTimeout ( 300 ); const box = await el. boundingBox (); if (box) { await page. mouse . move (box. x + box. width / 2 , box. y + box. height / 2 , { steps : 10 }); await page. waitForTimeout ( 400 ); } await el. click (clickOpts); } catch (e) { console . error ( `WARNING: moveAndClick failed on " ${label} ": ${e.message} ` ); return false ; } await page. waitForTimeout (postClickDelay); return true ; } 每次调用都应包含描述性的 label 以便调试。 5. 输入 可见地输入,而非瞬间填充: async function typeSlowly ( page, locator, text, label, charDelay = 35 ) { const el = typeof locator === 'string' ? page. locator (locator). first () : locator; const visible = await el. isVisible (). catch ( () => false ); if (!visible) { console . error ( `WARNING: typeSlowly skipped - " ${label} " not visible` ); return false ; } await moveAndClick (page, el, label); await el. fill ( '' ); await el. pressSequentially (text, { delay : charDelay }); await page. waitForTimeout ( 500 ); return true ; } 6. 滚动 使用平滑滚动而非跳跃: await page. evaluate ( () => window . scrollTo ({ top : 400 , behavior : 'smooth' })); await page. waitForTimeout ( 1500 ); 7. 仪表盘平移 展示仪表盘或概览页面时,将光标移过关键元素: async function panElements ( page, selector, maxCount = 6 ) { const elements = await page. locator (selector). all (); for ( let i = 0 ; i < Math . min (elements. length , maxCount); i++) { try { const box = await elements[i]. boundingBox (); if (box && box. y < 700 ) { await page. mouse . move (box. x + box. width / 2 , box. y + box. height / 2 , { steps : 8 }); await page. waitForTimeout ( 600 ); } } catch (e) { console . warn ( `WARNING: panElements skipped element ${i} (selector: " ${selector} "): ${e.message} ` ); } } } 8. 字幕 在视口底部注入一个字幕栏: async function injectSubtitleBar ( page ) { await page. evaluate ( () => { if ( document . getElementById ( 'demo-subtitle' )) return ; const bar = document . createElement ( 'div' ); bar. id = 'demo-subtitle' ; bar. style . cssText = ` position: fixed; bottom: 0; left: 0; right: 0; z-index: 999998; text-align: center; padding: 12px 24px; background: rgba(0, 0, 0, 0.75); color: white; font-family: -apple-system, "Segoe UI", sans-serif; font-size: 16px; font-weight: 500; letter-spacing: 0.3px; transition: opacity 0.3s; pointer-events: none; ` ; bar. textContent = '' ; bar. style . opacity = '0' ; document . body . appendChild (bar); }); } async function showSubtitle ( page, text ) { await page. evaluate ( ( t ) => { const bar = document . getElementById ( 'demo-subtitle' ); if (!bar) return ; if (t) { bar. textContent = t; bar. style . opacity = '1' ; } else { bar. style . opacity = '0' ; } }, text); if (text) await page. waitForTimeout ( 800 ); } 每次导航后,将 injectSubtitleBar(page) 与 injectCursor(page) 一起调用。 使用模式: await showSubtitle (page, 'Step 1 - Logging in' ); await showSubtitle (page, 'Step 2 - Dashboard overview' ); await showSubtitle (page, '' ); 指南: 保持字幕文本简短,最好在 60 个字符以内。 使用 Step N - Action 格式以保持一致性。 在长时间暂停且界面可以自我说明时清除字幕。 脚本模板 'use strict' ; const { chromium } = require ( 'playwright' ); const path = require ( 'path' ); const fs = require ( 'fs' ); const BASE_URL = process. env . QA_BASE_URL || 'http://localhost:3000' ; const VIDEO_DIR = path. join (__dirname, 'screenshots' ); const OUTPUT_NAME = 'demo-FEATURE.webm' ; const REHEARSAL = process. argv . includes ( '--rehearse' ); // Paste injectCursor, injectSubtitleBar, showSubtitle, moveAndClick,
Keywords that activate this skill. Click one to copy it.

This skill does not provide trigger words.

The downloaded .skill package contains the following fields.
Field Description
formatFormat tag (skill/v1)
skill_idUnique skill ID
nameSkill name
versionVersion
descriptionDescription
categoryCategories (array)
trigger_wordsTrigger words
tagsTags
sourceSource
source_urlSource URL (this page)
exported_atExported at (set per download)
system_promptSystem prompt body
model_configModel config: provider / model / temperature / max_tokens / top_p
examplesExamples
install_guideImport guide for Coze / Dify / Claude / custom frameworks
The same skill can be exported in different platform formats.
.skill Standard format with system_prompt and model_config, ready for any agent framework Download
.skillpro Enhanced format with scripts, tools, dependencies and hooks Download
.json Plain JSON export with system_prompt and model parameters only Download
Coze Markdown with frontmatter, for Coze platform import Download
Dify Dify DSL, import directly after creating an app Download

每日精选 Skill 推荐,免费送到你邮箱

输入邮箱,每天接收一个精选 AI Agent 技能推荐。完全免费,持续更新。

提交后我们会发送一封确认邮件,点击邮件里的链接才会开始收信。

完全免费,取消任意时间。我们不会发送垃圾邮件。