\\n <meta name=\\"viewport\\" content=\\"width=device-width, initial-scale=1.0\\" />\\n <title>Audio</title>\\n </head>\\n <body>\\n <script>\\n const concatAudioFilesOnBrowser = async (audioFiles) => {\\n const script = document.createElement(\'script\')\\n script.src = \'https://unpkg.com/@ffmpeg/ffmpeg@0.11.6/dist/ffmpeg.min.js\'\\n document.head.appendChild(script)\\n await new Promise((resolve) => (script.onload = resolve))\\n\\n const { createFFmpeg, fetchFile } = FFmpeg\\n const ffmpeg = createFFmpeg({ log: true })\\n\\n await ffmpeg.load()\\n\\n // Download and write each file to FFmpeg\'s virtual file system\\n for (const [index, audioFile] of audioFiles.entries()) {\\n const audioData = await fetchFile(audioFile)\\n ffmpeg.FS(\'writeFile\', `input${index}.mp3`, audioData)\\n }\\n\\n // Create a file list for ffmpeg concat\\n const fileList = audioFiles.map((_, i) => `file \'input${i}.mp3\'`).join(\'\\\\n\')\\n ffmpeg.FS(\'writeFile\', \'filelist.txt\', fileList)\\n\\n // Execute FFmpeg command to concatenate files\\n await ffmpeg.run(\\n \'-f\',\\n \'concat\',\\n \'-safe\',\\n \'0\',\\n \'-i\',\\n \'filelist.txt\',\\n \'-c:a\',\\n \'libmp3lame\',\\n \'-q:a\',\\n \'5\',\\n \'output.mp3\',\\n )\\n\\n // Read the output file\\n const data = ffmpeg.FS(\'readFile\', \'output.mp3\')\\n\\n // Create a downloadable link\\n const blob = new Blob([data.buffer], { type: \'audio/mp3\' })\\n\\n // Clean up\\n audioFiles.forEach((_, i) => {\\n ffmpeg.FS(\'unlink\', `input${i}.mp3`)\\n })\\n ffmpeg.FS(\'unlink\', \'filelist.txt\')\\n ffmpeg.FS(\'unlink\', \'output.mp3\')\\n\\n return blob\\n }\\n </script>\\n </body>\\n</html>\\n
export async function concatAudioFiles(audioFiles: string[], BROWSER: Fetcher, { workerUrl }: { workerUrl: string }) {\\n const browser = await puppeteer.launch(BROWSER)\\n const page = await browser.newPage()\\n await page.goto(`${workerUrl}/audio`)\\n\\n console.info(\'start concat audio files\', audioFiles)\\n const fileUrl = await page.evaluate(async (audioFiles) => {\\n // 此处 JS 运行在浏览器中\\n // @ts-expect-error 浏览器内的对象\\n const blob = await concatAudioFilesOnBrowser(audioFiles)\\n\\n const result = new Promise((resolve, reject) => {\\n const reader = new FileReader()\\n reader.onloadend = () => resolve(reader.result)\\n reader.onerror = reject\\n reader.readAsDataURL(blob)\\n })\\n return await result\\n }, audioFiles) as string\\n\\n console.info(\'concat audio files result\', fileUrl.substring(0, 100))\\n\\n await browser.close()\\n\\n const response = await fetch(fileUrl)\\n return await response.blob()\\n}\\n\\nconst audio = await concatAudioFiles(audioFiles, env.BROWSER, { workerUrl: env.HACKER_NEWS_WORKER_URL })\\nreturn new Response(audio)
\\n上面的代码基本是 Cursor 写的,最终效果可以去 Hacker News 代码仓库 看。
AI 驱动的博客排版
使用人工智能技术重新定义博客文章的视觉呈现,从简约到精致,从平凡到卓越。
之前 Claude 3.7 发布,用它生成了一个邮箱 App 的设计图,效果还不错。
这周在 Twitter 上看到乔木老师分享的生成网页效果、生成PPT、生成3D教学动画、生成SVG海报的提示词,感觉很有意思。
这个周末把博客引擎升级了一下,使用 Astro + TailwindCSS + MDX 重构了博客。由于 MDX 有更强的扩展性,所以博客文章正文几乎可以展示任何内容。
于是使用乔木老师分享的提示词,修改了以后,用来生成博客文章的排版。效果出乎意料,从此不再为博客文章的排版而烦恼。
对比图
更多展示效果
# 生成文章网页\\n\\n你是一名专业的网页设计师和前端开发专家,对现代 Web 设计趋势和最佳实践有深入理解,尤其擅长创造具有极高审美价值的用户界面。你的设计作品不仅功能完备,而且在视觉上令人惊叹,能够给用户带来强烈的\\"Aha-moment\\"体验。\\n\\n请根据最后提供的内容,设计一个**美观、现代、易读**的\\"中文\\"可视化网页。请充分发挥你的专业判断,选择最能体现内容精髓的设计风格、配色方案、排版和布局。\\n\\n**设计目标:**\\n\\n* **视觉吸引力:** 创造一个在视觉上令人印象深刻的网页,能够立即吸引用户的注意力,并激发他们的阅读兴趣。\\n* **可读性:** 确保内容清晰易读,无论在桌面端还是移动端,都能提供舒适的阅读体验。\\n* **信息传达:** 以一种既美观又高效的方式呈现信息,突出关键内容,引导用户理解核心思想。\\n* **情感共鸣:** 通过设计激发与内容主题相关的情感(例如,对于励志内容,激发积极向上的情绪;对于严肃内容,营造庄重、专业的氛围)。\\n\\n**设计指导(请灵活运用,而非严格遵循):**\\n\\n* **整体风格:**\\n * 可以考虑杂志风格、出版物风格,或者其他你认为合适的现代 Web 设计风格。\\n * 配色参考 shadcn ui 的 Zinc 主题配色。\\n * 目标是创造一个既有信息量,又有视觉吸引力的页面,就像一本精心设计的数字杂志或一篇深度报道。\\n* **Hero 模块(可选,但强烈建议):**\\n * 如果你认为合适,可以设计一个引人注目的 Hero 模块。\\n * 它可以包含标题(h2)、副标题(p)、一段引人入胜的引言。\\n* **排版:**\\n * 精心选择字体组合(衬线和无衬线),以提升中文阅读体验。\\n * 利用不同的字号、字重、颜色和样式,创建清晰的视觉层次结构。\\n * 可以考虑使用一些精致的排版细节(如首字下沉、悬挂标点)来提升整体质感。\\n * Tabler icon 中有很多图标,选合适的点缀增加趣味性。 使用实例 `icon-[tabler--名称]`\\n* **配色方案:**\\n * 选择一套既和谐又具有视觉冲击力的配色方案。\\n * 考虑使用高对比度的颜色组合来突出重要元素。\\n * 可以探索渐变、阴影等效果来增加视觉深度。\\n* **布局:**\\n * 使用基于网格的布局系统来组织页面元素。\\n * 充分利用负空间(留白),创造视觉平衡和呼吸感。\\n * 可以考虑使用卡片、分割线、图标等视觉元素来分隔和组织内容。\\n* **调性:**整体风格精致, 营造一种高级感。\\n\\n**技术规范:**\\n\\n* 使用 tailwindCSS 定义样式。\\n* 不使用 JS , HTML 和 CSS 优先。\\n* 只生成正文区域,网页已经使用 `prose` 类包裹了, 可以使用 `not-prose` 突破限制。\\n* 实现完整的深色/浅色模式切换功能。\\n* 代码结构清晰、语义化,包含适当的注释。\\n* 实现完整的响应式,必须在所有设备上(手机、平板、桌面)完美展示。\\n\\n**额外加分项:**\\n\\n* **微交互:** 添加微妙而有意义的微交互效果来提升用户体验(例如,按钮悬停效果、卡片悬停效果、页面滚动效果)。\\n* **补充信息:** 可以主动搜索并补充其他重要信息或模块(例如,关键概念的解释、相关人物的介绍等),以增强用户对内容的理解。\\n\\n**输出要求:**\\n\\n* 输出一个独立的 MDX 文件,生成的语法符合 MDX 规范和 JSX 规范。\\n* 修改范围不要超出 mdx 文件。\\n* 不要在正文中出现标签,发布时间相关的信息。\\n* 外链增加 `nofollow`, 在新窗口打开, 保留 `title` 属性。\\n* 代码块不做任何修改,依旧使用 md 格式。\\n* 确保代码符合 W3C 标准,没有错误或警告。\\n\\n请你像一个真正的设计师一样思考,充分发挥你的专业技能和创造力,打造一个令人惊艳的网页!\\n\\n待处理内容:@miantiao_me
项目背景
Hacker News 是我一直关注的重要资讯来源,它能持续提供新奇有趣的极客资讯。之前我每天都会花半小时左右的时间来浏览。
过年期间,我注意到 DeepSeek 非常火,就尝试使用 Cloudflare Workflow 编写了一个工作流。但生成的内容都很短,而且接口也不稳定。后来尝试 GPT 4.0 系列模型,效果也不理想,于是就暂时搁置了。
Gemini 2.0 发布后,我尝试了一下,发现生成文章的效果还不错。于是我便开发了一个 Web 界面,并加入了 RSS 订阅功能,这样就可以在上班路上用泛用型播客 App 收听 Hacker News 的资讯了。
主要特性
技术栈
工作流程
每日自动收集 Hacker News 上最受欢迎的帖子
通过 Gemini 2.0 AI 将英文内容智能翻译并总结为中文
将生成的文本转换为自然流畅的语音播报
将生成的内容和音频存储在高效、低成本的云端存储系统中
用户可以通过网页浏览或在任何播客应用中订阅收听
未来计划
目前 TTS 使用的是 Edge TTS,只有一个女声。理想情况下,使用男声和女声进行对话的形式可能会更好。豆包的 TTS 音色很不错,但它是收费的。等后续有时间,我会考虑改进这部分。
推荐工具
最后,推荐一下 Cloudflare Workflow,一个很棒的 Workflow 运行平台。