Files
memind/scripts/thumbnail-preview-demo.mjs
john e2ad3bf62b feat(mindspace): 公开页分享组件、workspace 路径与 Plaza 发布增强
- 新增 public share widget 与 workspace relative path 解析
- 增强 publications/chat-plaza 发布链路与缩略图 demo
- 补充 schema、cover 检查脚本与回归测试

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-05 23:14:41 +08:00

135 lines
5.7 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env node
import fs from 'node:fs/promises';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import {
buildFeedThumbnailSvg,
extractCoverSignals,
generateHtmlThumbnail,
resolveCoverDataUri,
} from '../mindspace-thumbnails.mjs';
const root = path.join(path.dirname(fileURLToPath(import.meta.url)), '..');
const sampleDir = path.join(root, 'public', 'thumbnail-demo-samples');
const outputDir = path.join(root, 'public', 'thumbnail-demo');
/** A/B pairs: bad = 缺 mindspace-covergood = 方案 A 完整元数据 + hero 图 */
const pairs = [
{
id: 'vietnam-guide',
label: '越南旅游指南',
bad: path.join(sampleDir, 'vietnam-guide-bad.html'),
good: path.join(sampleDir, 'vietnam-guide-good.html'),
},
];
function galleryCard({ id, title, subtitle, variant }) {
return `
<article class="feed-card">
<div class="feed-cover"><img src="./${id}-${variant}-feed.svg" alt="${title} 预览图" /></div>
<div class="feed-body">
<h3>${title}</h3>
<p>${subtitle}</p>
</div>
</article>`;
}
async function renderVariant({ htmlPath, variant, id }) {
const html = await fs.readFile(htmlPath, 'utf8');
const signals = extractCoverSignals(html);
const coverDataUri = signals.image
? await resolveCoverDataUri({
storageRoot: outputDir,
contentBaseDir: path.dirname(htmlPath),
imageUrl: signals.image,
})
: null;
const feedSvg = buildFeedThumbnailSvg(signals, { coverDataUri });
const feedPath = path.join(outputDir, `${id}-${variant}-feed.svg`);
await fs.writeFile(feedPath, feedSvg, 'utf8');
await generateHtmlThumbnail(outputDir, `${id}-${variant}-generated.svg`, html, {
contentBaseDir: path.dirname(htmlPath),
title: signals.title,
subtitle: signals.subtitle,
});
return { signals, hasPhoto: Boolean(coverDataUri) };
}
async function main() {
await fs.mkdir(outputDir, { recursive: true });
const sections = [];
for (const pair of pairs) {
const bad = await renderVariant({ htmlPath: pair.bad, variant: 'bad', id: pair.id });
const good = await renderVariant({ htmlPath: pair.good, variant: 'good', id: pair.id });
sections.push(`
<section class="sample">
<h2>${pair.label}</h2>
<p class="hint">左侧:未写 mindspace-cover(退化为纯色渐变) · 右侧:方案 A(完整元数据 + hero 主图)</p>
<div class="pair">
<div class="thumb">
<img src="./${pair.id}-bad-feed.svg" alt="缺 cover" />
<span class="bad">❌ 缺 mindspace-cover · ${bad.hasPhoto ? '有图' : '无图'} · tag=${bad.signals.tag}</span>
</div>
<div class="thumb">
<img src="./${pair.id}-good-feed.svg" alt="方案 A" />
<span class="good">✅ 方案 A · ${good.hasPhoto ? '照片封面' : '场景/渐变'} · tag=${good.signals.tag}</span>
</div>
</div>
<div class="feed-grid">
${galleryCard({ id: pair.id, title: pair.label, subtitle: '缺 cover · 信息流效果', variant: 'bad' })}
${galleryCard({ id: pair.id, title: pair.label, subtitle: '方案 A · 信息流效果', variant: 'good' })}
</div>
</section>`);
}
const html = `<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>MindSpace 缩略图 · 方案 A 对比</title>
<style>
:root { color-scheme: light; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; background: #f4f0e7; color: #18211d; }
body { margin: 0; padding: 24px; max-width: 960px; }
h1 { margin: 0 0 8px; font-size: 28px; }
.lead { margin: 0 0 24px; color: #5f6963; line-height: 1.6; }
.compare { display: grid; gap: 28px; }
.sample { padding: 20px; border-radius: 20px; background: #fff; box-shadow: 0 10px 30px rgba(24,33,29,0.08); }
.sample h2 { margin: 0 0 8px; font-size: 20px; }
.hint { margin: 0 0 16px; font-size: 13px; color: #6d7771; }
.pair { display: grid; grid-template-columns: 1fr 1fr; gap: 18px; }
.thumb { display: grid; gap: 8px; }
.thumb img { width: 100%; max-width: 270px; border-radius: 12px; box-shadow: 0 8px 24px rgba(24,33,29,0.12); }
.thumb span { font-size: 12px; line-height: 1.5; }
.thumb span.bad { color: #b45309; }
.thumb span.good { color: #2f6f57; }
.feed-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; max-width: 520px; margin-top: 18px; }
.feed-card { border-radius: 12px; overflow: hidden; background: #fff; box-shadow: 0 2px 10px rgba(24,33,29,0.07); }
.feed-cover { aspect-ratio: 3 / 4; background: #ece7df; }
.feed-cover img { width: 100%; height: 100%; object-fit: cover; display: block; }
.feed-body { padding: 10px 12px 12px; }
.feed-body h3 { margin: 0; font-size: 14px; line-height: 1.45; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
.feed-body p { margin: 6px 0 0; font-size: 12px; color: #939c97; }
code { background: #f0ebe3; padding: 2px 6px; border-radius: 4px; font-size: 12px; }
</style>
</head>
<body>
<h1>方案 Amindspace-cover 对比 Demo</h1>
<p class="lead">
页面生成时在 <code>&lt;head&gt;</code> 写入 <code>mindspace-cover</code> 元数据并引用 hero 主图,
缩略图会从「纯色渐变」升级为「照片封面 + 标题」。
交付前可跑 <code>npm run check:mindspace-cover</code> 自检。
</p>
<div class="compare">${sections.join('')}</div>
</body>
</html>`;
await fs.writeFile(path.join(outputDir, 'index.html'), html, 'utf8');
console.log(`已生成对比页: ${path.join(outputDir, 'index.html')}`);
console.log('本地查看: http://localhost:5173/thumbnail-demo/');
}
await main();