DeepSeek Harness Hub
← 返回列表

幻灯片代码框架onetest-ai/octodeck

DeepSeek Harnessspec-screened在 GitHub 查看 ↗
未验证

用 TypeScript 组件编写可交互演示网页并导出单文件

尚未跑自动兼容性验证,可查看页面内的依赖与入口分析。 · 最近上游提交 2026/8/28 · 已提供中文文档

Octodeck — 幻灯片即代码:将演示幻灯片构建为带类型的 TypeScript 组件,并配备一个 DeepSeek Harness 插件,让智能体能够实时创作它们。

综合分
28
GitHub 分
28
用户评分
★ Stars
0
周下载量
安装插件(需先安装 dsh CLI 引擎:npm install -g @deepseek-ai/dsh)
dsh plugin --profile web add onetest-ai/octodeck
该插件未发布到 npm,走 GitHub 源安装(pnpm 若拦截 prepare 脚本,按其提示在 pnpm-workspace.yaml 的 allowBuilds 中放行后重跑)
数据截至 2026/9/16(元数据每日更新 · 实装验证按队列轮转,单条结论的验证时间见上方)
用户评分
还没有人投票,来当第一个
订阅周报,不错过优质插件更新
每周一封 · 高评分插件 + 新用户活动

README

Octodeck

一个用于构建演示类网页的微型 TypeScript + Vite 框架。
幻灯片就是普通的 TS 组件——(ctx) => HTMLElement——因此它们有类型、
支持热重载,并且可以完全交互。

快速开始

npm install
npm run dev               # http://localhost:9001
npm run new:deck -- # 搭建一个幻灯片组 → /.html
npm run build             # 类型检查 + 生产打包到 dist/
npm run preview           # 在 9001 端口上提供构建后的打包产物
DECK= npm run build:single  # 将一个幻灯片组编译为单个自包含 HTML(dist-single/.html)

开发服务器运行在 9001 端口(在 vite.config.ts 中配置)。

自包含单文件导出

DECK= npm run build:single 会生成 dist-single/.html——一个将
所有 JS、CSS、favicon 和网页字体全部内联的单一文件(零外部引用;可在任何浏览器中
离线打开)。它会先运行单文件 Vite 构建
(vite.singlefile.config.ts,通过 vite-plugin-singlefile),然后运行 scripts/inline-single.mjs
(内联 favicon,并将 Google 字体抓取/嵌入为 data URI)。DECK 环境变量
用于选择要打包哪个 .html 入口。(字体嵌入在构建时需要网络;如果离线,
则会降级为远程字体。)

DeepSeek Harness 插件

packages/dsh-deck 就是 @onetest/dsh-deck:一个可安装的 DeepSeek Harness 插件,可在 harness 会话内部构建这些幻灯片组。智能体会创建一个幻灯片组,将其幻灯片编写为 TypeScript,而幻灯片组会在其工作时渲染到悬浮于对话之上的画布上。

它创建的幻灯片组位于会话所选工作区下的 .deck// 中,并且是普通的 Octodeck 幻灯片组——使用下文记录的同一套框架、模板和主题,并可用相同方式导出。

npm run build  --workspace @onetest/dsh-deck
npm run bundle --workspace @onetest/dsh-deck

其 README 涵盖了如何安装到 harness 配置、Deck creator 智能体预设以及发布。

编写幻灯片

编辑 src/slides/index.ts。导出的 slides 数组中的每一项都是一个
返回 DOM 节点的组件:

import { h } from '../framework'
import type { Slide } from '../framework'

export const slides: Slide[] = [
// 一个节点——使用内置的排版默认值。
() => h('h1', 'Hello'),

// 上下文会提供位置 + 导航信息。
(ctx) => h('p', Slide ${ctx.index + 1} of ${ctx.total}),

// 分步显示:任何 [data-fragment] 都会在按下下一个 → 时显示。
() => h('ul', {},
h('li', { 'data-fragment': true }, 'appears first'),
h('li', { 'data-fragment': true }, 'then this'),
),

// 元数据形式:附加演讲者备注或每张幻灯片的 class。
() => ({ el: h('h2', 'Closing'), notes: 'Thank the audience', class: 'dark' }),
]

因为幻灯片是函数,所以每次显示时它们都会重新渲染——
交互式组件(事件监听器、定时器、获取的数据)每次都会重新开始
访问。参见点击计数器幻灯片以获取示例。

幻灯片模板与布局组件

与其用原始的 h() 手工编写每一张幻灯片,不如从内置库中组合(全部从 ./framework 导出,定义在 src/framework/components.ts 中)。

幻灯片模板 —— 填满整张幻灯片:

| 模板 | 插槽 |
| --- | --- |
| TitleSlide | eyebrow?、title、subtitle?、footer? |
| SectionSlide | number?、title、subtitle?(章节分隔页) |
| StatementSlide | text、cite?(一句大号引用语) |
| HeaderSlide | { title, subtitle?, kicker? }, ...body(主力模板) |

布局组件 —— 在模板内部组合使用:

Columns({ cols: '65/35' }, left, right)   // 也可:2 | 3 | [2,1] | '33/33/33'
Grid({ cols: 3 }, ...cells)               // 均匀的 N 列网格
Stack({ gap: '1rem' }, ...items)          // 垂直堆叠

Columns 是一个参数化组件,可满足你想要的每一种比例:

| 你想要 | cols: |
| --- | --- |
| 50 / 50 | 2 或 '50/50' |
| 33 / 33 / 33 | 3 或 '33/33/33' |
| 65 / 35 | '65/35' |
| 70 / 30 | '70/30' |
| 2 : 1 | [2, 1] |

内容组件: Heading、Subheading、Kicker、Note、
Bullets(items, { fragment?, ordered? })、Card({ title?, accent? }, ...)、
Metric({ value, label })、Image({ src, alt?, caption? })、Code(src, { lang? })。

演示文稿原型布局(仿照真实演示文稿中的模式):

// Split —— 内容面板 + 全出血媒体(经典的标题/特性幻灯片)
SplitSlide({ ratio: '60/40', side: 'right', media: Image({ src }) }, Heading('…'), Bullets([…]))

// Bento —— 非对称跨格网格;每个瓦片声明自己的尺寸
Bento({ cols: 4, rows: 2 },
Tile({ span: 2, rowSpan: 2 }, Metric({ value: '10×', label: 'faster' })),
Tile({ span: 2 }, …), Tile({}, …), Tile({}, …))

// Sequence —— 流程 / 时间线 / 议程(一个原语,两种方向)
Sequence({ orientation: 'horizontal' },          // 垂直 → 议程/目录
Step({ label: 'Stage 01', title: 'Ideate' }, '…'),
Step({ label: 'Stage 02', title: 'Create' }, '…'))

// Quote —— 带署名的推荐语
Quote({ author: 'Stacey', role: 'Founder', avatar }, 'This is genius.')

// LogoWall —— 弱化的合作伙伴/客户徽标
LogoWall([{ src }, { src }])

Bento/Tile 复用了面板材质角色,因此每种原型都能自动适配主题。

图表 —— 确定性且主题感知(没有自动布局引擎)。你将每个节点放置在网格单元上;边会在它们之间自动正交布线:

Diagram({
cols: 3, rows: 3,
nodes: {
client: { at: [0, 1], label: 'Client', shape: 'pill' },
api:    { at: [1, 1], label: 'API gateway', accent: true },
db:     { at: [2, 0], label: 'Database' },
cache:  { at: [2, 1], label: 'Cache' },
},
edges: [
{ from: 'client', to: 'api', label: 'HTTPS' },
{ from: 'api', to: 'db', label: 'SQL' },
{ from: 'api', to: 'cache', dashed: true },
],
})
渲染为使用契约颜色的单个响应式 SVG,因此它会匹配当前主题(扁平盒子、玻璃等)。可预测,因为布局由你决定——渲染之间不会有任何偏移。对于大型自动布局的图形,同样的 {nodes, edges} 规范之后可以交给 elkjs 来计算 at 位置。

把它们组合起来:

() => HeaderSlide({ kicker: 'Roadmap', title: 'Where we’re headed' },
Columns({ cols: '65/35', align: 'center' },
Bullets([
'Ship the editor',
'Add presenter view',
'Theme marketplace',
], { fragment: true }),
Card({ title: 'Now', accent: true }, Metric({ value: 'Q3', label: 'GA target' })),
),
)

src/slides/index.ts 是上述所有内容的可用画廊。

主题

主题通过填充契约——src/framework/contract.css 中的 --octo- 变量——来为整个演示文稿重新设定样式。组件只读取这些变量,因此主题永远不会触及组件代码。两个维度:

- data-theme —— 身份标识(字体、几何形状、材质风格、强调色)
- data-mode —— light / dark 极性(仅翻转种子颜色)

两者都应用于 。通过演示文稿切换:

import { themeList } from './themes'

const d = deck(slides, { themes: themeList, theme: 'primer', mode: 'light' }).start()
d.setTheme('octo-glass')   // load + apply (CSS is code-split, fetched on first use)
d.toggleMode()             // light ⇄ dark
d.cycleTheme()             // next theme

用于预览的 URL 覆盖:?theme=radiant&mode=dark。按键:t 循环切换主题,d 切换模式。

内置主题

| id | 外观 | 模式 |
| --- | --- | --- |
| midnight | 干净的扁平深色(默认) | dark · light |
| protocol | 扁平技术文档风 | dark · light |
| primer | 编辑风粗体,柔和阴影 | light · dark |
| radiant | 渐变/辉光,大号展示 | light · dark |
| commit | 深色优先扁平(草稿) | dark · light |
| octo-glass | Liquid Glass(内置 @octo/ui tokens) | dark · light |

编写主题

大多数主题是数据,而非代码——在你的作用域下填充契约:

/ src/themes/sunrise/theme.css /
[data-theme="sunrise"] {                         / identity (mode-independent) /
--octo-font: 'Fraunces', serif;
--octo-radius: 18px;
}
[data-theme="sunrise"][data-mode="light"] {      / polarity seeds — rest derives */
--octo-bg: #fdfcf9; --octo-fg: #2b2118; --octo-accent: #e0644b;
}
[data-theme="sunrise"][data-mode="dark"] {
--octo-bg: #1a1410; --octo-fg: #f3ece2; --octo-accent: #ff7a5c;
}

表面、弱化文本、边框和卡片填充通过 color-mix 从 bg/fg/accent 派生,因此只需几个种子颜色就能让浅色和深色保持协调。对于更丰富的主题,还要填充材质层——--octo-panel-bg、--octo-panel-shadow、--octo-panel-backdrop(模糊)、--octo-panel-sheen、--octo-backdrop、--octo-heading-fill(渐变文本)。这就是玻璃/渐变外观所需的全部——无需组件 CSS。然后在 src/themes/index.ts 中注册它。
从线上站点逆向工程一个主题

大多数真实主题是从渲染后的页面中恢复的,而不是从 token 源文件:

npx playwright install chromium          # 一次性
npm run extract-theme -- https://primer.tailwindui.com primer

它会加载该 URL(跟随内嵌的预览 iframe),在浅色和深色模式下扫描计算样式中的颜色/字体/材质,并写出
src/themes/primer/{theme.draft.css, tokens.json, reference.png}。对照截图审查草稿,挑选真正的强调色,然后重命名为 theme.css。

h() 辅助函数

h('div', { class: 'card', onClick: () => ... }, h('h3', 'Title'), 'text child')

- 第二个参数会被视为 props 或一个子节点(因此 h('h1', 'text') 可以正常工作)。
- onX 键会变成事件监听器;style 接受字符串或对象。
- raw('…') 会从 HTML 字符串构建一个片段。

导航(免费,内置)

| 按键 | 操作 |
| --- | --- |
| → / Space / PageDown | 下一个片段,然后下一张幻灯片 |
| ← / PageUp | 上一个片段,然后上一张幻灯片 |
| Home / End | 第一张 / 最后一张幻灯片 |
| F | 切换全屏 |

另外:在触摸设备上左右滑动,以及通过 URL hash 进行深度链接
(#/3 跳转到第 3 张幻灯片)。

配置

import { deck } from './framework'
import { slides } from './slides'

deck(slides, {
mount: '#deck',        // 选择器或元素
showProgress: true,    // “3 / 12”读数
showProgressBar: true, // 顶部细条
hashRouting: true,     // 将幻灯片同步到 URL hash
touch: true,           // 滑动导航
loop: false,           // 越过最后一张幻灯片后循环
}).start()

主题化

在你自己的 CSS 中任意位置覆盖来自 src/framework/styles.css
的 CSS 自定义属性(--octo-bg、--octo-accent、--octo-font、……)。

布局

src/
framework/        可复用引擎(可放入任何项目)
deck.ts         Deck 类 — 导航、渲染、片段、路由
h.ts            hyperscript DOM 辅助函数
types.ts        Slide / SlideContext / DeckOptions
styles.css      基础主题
index.ts        公共 API 桶文件
slides/index.ts   你的 deck 放在这里
main.ts           启动引导
app.css           示例幻灯片的样式

上游仓库有新提交时邮件通知你(每天最多一封,无更新不打扰),随时一键退订。

💬 加入 DPharness 群聊

插件用法、部署报错、新插件第一时间同步——群里问,比一个人翻文档快。

点击加入 QQ 群
DPharness 群聊二维码,手机 QQ 扫码进群
扫码进群