Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 14 additions & 2 deletions .github/workflows/docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,20 @@ jobs:
curl -fsSL https://d2lang.com/install.sh | sh -s --
d2 version

- name: Build package and docs assets
run: npm run docs:build
- name: Install ffmpeg
run: sudo apt-get install -y ffmpeg

- name: Install Playwright browsers
run: npx playwright install --with-deps chromium

- name: Build package
run: npm run build

- name: Record demo GIF
run: node scripts/record-demo.mjs --out docs-site/public/demo.gif

- name: Build docs assets and site
run: npm run docs:prepare-assets && npm --prefix docs-site run build

- name: Setup Pages
uses: actions/configure-pages@v4
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ Docs: `https://diascope.biolytics.ai`

## How it looks

![DiaScope walkthrough](docs/demo.gif)

Each story is a full-screen page: diagram on the left, narration panel on the right. Each step highlights the relevant nodes, pans/zooms to them, and shows the title + body text. Click a node for detail. Press `→` to advance.

## Interactive features
Expand Down
8 changes: 4 additions & 4 deletions docs-site/public/examples/vllm/deployment.html
Original file line number Diff line number Diff line change
Expand Up @@ -205,13 +205,13 @@
</div>
<div id="panel">
<div id="step-nav-bar">
<div id="step-nav"><button class="step-btn" data-step="all">All</button>
<button class="step-btn" data-step="0">01</button>
<div id="step-nav"><button class="step-btn" data-step="0">01</button>
<button class="step-btn" data-step="1">02</button>
<button class="step-btn" data-step="2">03</button>
<button class="step-btn" data-step="3">04</button>
<button class="step-btn" data-step="4">05</button>
<button class="step-btn" data-step="5">06</button></div>
<button class="step-btn" data-step="5">06</button>
<button class="step-btn" data-step="all">All</button></div>
<button id="btn-panel-toggle" type="button" title="Collapse narration" aria-label="Collapse narration" aria-expanded="true">></button>
</div>
<div id="step-content">
Expand Down Expand Up @@ -1088,7 +1088,7 @@
"provider access and facility trail": "Infrastructure-side evidence remains necessary even when the application stack is self-managed."
},
"overview": {
"position": "first",
"position": "last",
"title": "Compliant GPU Blueprint",
"body": "A step-by-step walkthrough of what a compliant external GPU deployment looks like\nfor clinical AI: who controls what, where data crosses boundaries, and what evidence\nmust be in place before production traffic is allowed.\n\nUse the numbered steps to walk through the key decision points, or click any node\nfor detail on that component.\n"
}
Expand Down
Binary file added docs/demo.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
8 changes: 4 additions & 4 deletions examples/vLLM/deployment.html
Original file line number Diff line number Diff line change
Expand Up @@ -205,13 +205,13 @@
</div>
<div id="panel">
<div id="step-nav-bar">
<div id="step-nav"><button class="step-btn" data-step="all">All</button>
<button class="step-btn" data-step="0">01</button>
<div id="step-nav"><button class="step-btn" data-step="0">01</button>
<button class="step-btn" data-step="1">02</button>
<button class="step-btn" data-step="2">03</button>
<button class="step-btn" data-step="3">04</button>
<button class="step-btn" data-step="4">05</button>
<button class="step-btn" data-step="5">06</button></div>
<button class="step-btn" data-step="5">06</button>
<button class="step-btn" data-step="all">All</button></div>
<button id="btn-panel-toggle" type="button" title="Collapse narration" aria-label="Collapse narration" aria-expanded="true">></button>
</div>
<div id="step-content">
Expand Down Expand Up @@ -1088,7 +1088,7 @@
"provider access and facility trail": "Infrastructure-side evidence remains necessary even when the application stack is self-managed."
},
"overview": {
"position": "first",
"position": "last",
"title": "Compliant GPU Blueprint",
"body": "A step-by-step walkthrough of what a compliant external GPU deployment looks like\nfor clinical AI: who controls what, where data crosses boundaries, and what evidence\nmust be in place before production traffic is allowed.\n\nUse the numbered steps to walk through the key decision points, or click any node\nfor detail on that component.\n"
}
Expand Down
2 changes: 1 addition & 1 deletion examples/vLLM/deployment.story.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ meta:
d2_source: deployment.d2

overview:
position: first
position: last
title: "Compliant GPU Blueprint"
body: |
A step-by-step walkthrough of what a compliant external GPU deployment looks like
Expand Down
48 changes: 48 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@
"scripts": {
"build": "tsc && node esbuild.mjs",
"build:tsc": "tsc",
"docs:prepare-assets": "mkdir -p docs-site/public/examples/vllm && cp examples/vLLM/deployment.d2 docs-site/public/examples/vllm/deployment.d2 && cp examples/vLLM/deployment.story.yaml docs-site/public/examples/vllm/deployment.story.yaml && cp examples/vLLM/README.md docs-site/public/examples/vllm/README.md && node dist/cli/index.js build examples/vLLM/deployment.d2 examples/vLLM/deployment.story.yaml -o docs-site/public/examples/vllm/deployment.html && cp docs-site/public/examples/vllm/deployment.html examples/vLLM/deployment.html",
"demo:record": "node scripts/record-demo.mjs",
"docs:prepare-assets": "mkdir -p docs-site/public/examples/vllm && cp examples/vLLM/deployment.d2 docs-site/public/examples/vllm/deployment.d2 && cp examples/vLLM/deployment.story.yaml docs-site/public/examples/vllm/deployment.story.yaml && cp examples/vLLM/README.md docs-site/public/examples/vllm/README.md && node dist/cli/index.js build examples/vLLM/deployment.d2 examples/vLLM/deployment.story.yaml -o docs-site/public/examples/vllm/deployment.html && cp docs-site/public/examples/vllm/deployment.html examples/vLLM/deployment.html && ([ -f docs/demo.gif ] && cp docs/demo.gif docs-site/public/demo.gif || true)",
"docs:dev": "npm --prefix docs-site run dev",
"docs:build": "npm run build && npm run docs:prepare-assets && npm --prefix docs-site run build",
"test": "vitest run",
Expand All @@ -48,6 +49,7 @@
"@types/js-yaml": "^4.0.9",
"@types/node": "^20.0.0",
"esbuild": "^0.21.0",
"playwright": "^1.44.0",
"typescript": "^5.4.0",
"vitest": "^1.5.0"
}
Expand Down
139 changes: 139 additions & 0 deletions scripts/record-demo.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
#!/usr/bin/env node
/**
* Record a walkthrough GIF of the DiaScope viewer stepping through the vLLM example.
*
* Usage:
* node scripts/record-demo.mjs [--out <path>]
*
* Requires:
* - playwright (npm install)
* - npx playwright install chromium
* - ffmpeg on PATH
*
* Default output: docs/demo.gif
*/

import { chromium } from 'playwright';
import { execSync, spawnSync } from 'child_process';
import { mkdirSync, rmSync, existsSync, readdirSync } from 'fs';
import { join, resolve, dirname } from 'path';
import { fileURLToPath } from 'url';

const ROOT = resolve(fileURLToPath(new URL('.', import.meta.url)), '..');

// Parse --out flag
const outIdx = process.argv.indexOf('--out');
const OUT_GIF = outIdx !== -1
? resolve(process.argv[outIdx + 1])
: join(ROOT, 'docs', 'demo.gif');

const HTML = join(ROOT, 'examples', 'vLLM', 'deployment.html');
const FRAMES_DIR = join(ROOT, '.playwright-mcp', 'demo-recording');

// Viewport — wide enough to show both diagram and panel clearly.
const W = 1200;
const H = 680;

// Timing (ms)
const INITIAL_LOAD_MS = 1500; // viewer mount + first zoom-in
const FIRST_STEP_DWELL_MS = 1800;
const STEP_ANIM_MS = 1100; // zoom/pan animation per step
const STEP_DWELL_MS = 1400; // hold to read narration
const FINAL_DWELL_MS = 2200;

// GIF output settings
const GIF_FPS = 15;
const GIF_WIDTH = 1200; // keep at full width, GitHub renders it fine

function hasFfmpeg() {
return spawnSync('ffmpeg', ['-version'], { stdio: 'ignore' }).status === 0;
}

async function countSteps(page) {
return page.evaluate(() => {
const btns = [...document.querySelectorAll('#step-nav button')];
// exclude the "All" button
return btns.filter(b => b.textContent?.trim() !== 'All').length;
});
}

async function main() {
if (!existsSync(HTML)) {
console.error(`\nBuilt HTML not found at:\n ${HTML}\n\nRun first:\n npm run build\n npm run docs:prepare-assets\n`);
process.exit(1);
}

if (!hasFfmpeg()) {
console.error('\nffmpeg is required but not found on PATH.\n macOS: brew install ffmpeg\n Ubuntu: sudo apt-get install -y ffmpeg\n');
process.exit(1);
}

mkdirSync(FRAMES_DIR, { recursive: true });
// Clean any previous recording artefacts
for (const f of readdirSync(FRAMES_DIR)) rmSync(join(FRAMES_DIR, f), { recursive: true });

mkdirSync(dirname(OUT_GIF), { recursive: true });

console.log('Launching browser…');
const browser = await chromium.launch();
const context = await browser.newContext({
viewport: { width: W, height: H },
recordVideo: { dir: FRAMES_DIR, size: { width: W, height: H } },
});
const page = await context.newPage();

console.log(`Opening ${HTML}`);
await page.goto(`file://${HTML}`);

// Wait for the viewer shell to be ready
await page.waitForSelector('#btn-next', { state: 'visible' });
console.log(`Waiting ${INITIAL_LOAD_MS}ms for initial render + zoom-in…`);
await page.waitForTimeout(INITIAL_LOAD_MS);

const stepCount = await countSteps(page);
console.log(`Found ${stepCount} steps.`);

// Dwell on step 1
await page.waitForTimeout(FIRST_STEP_DWELL_MS);

for (let i = 1; i < stepCount; i++) {
console.log(`Step ${i + 1} / ${stepCount}`);
await page.click('#btn-next');
await page.waitForTimeout(STEP_ANIM_MS + STEP_DWELL_MS);
}

// Final dwell
await page.waitForTimeout(FINAL_DWELL_MS);

console.log('Closing browser (finalises video)…');
await context.close();
await browser.close();

// Locate the recorded .webm
const webm = readdirSync(FRAMES_DIR).find(f => f.endsWith('.webm'));
if (!webm) {
console.error('No .webm file found in recording dir.');
process.exit(1);
}
const webmPath = join(FRAMES_DIR, webm);
console.log(`Video recorded: ${webmPath}`);

// Convert to palette-optimised GIF
console.log(`Converting to GIF → ${OUT_GIF}`);
const ffmpegFilter = [
`fps=${GIF_FPS}`,
`scale=${GIF_WIDTH}:-2:flags=lanczos`,
`split[s0][s1]`,
`[s0]palettegen=stats_mode=full[p]`,
`[s1][p]paletteuse=dither=bayer:bayer_scale=5`,
].join(',');

execSync(
`ffmpeg -y -i "${webmPath}" -vf "${ffmpegFilter}" -loop 0 "${OUT_GIF}"`,
{ stdio: 'inherit' },
);

console.log(`\n✓ Done: ${OUT_GIF}`);
}

main().catch(e => { console.error(e); process.exit(1); });
Loading