H5录音、图文视频IndexDB储存最佳实践:用AI生成语音备忘录
引言
早在大学的时候,我就期望做一款属于自己的 APP,可惜那时不懂技术。现在有了技术,但却没有时间。好在 AI 的快速发展终于让我完成了这个愿望。于是,我用半天的时间,用 AI 生成了一个纯前端的 H5 程序:随心记。
在线体验地址:https://aicoding.juejin.cn/aicoding/work/7519454663779811366
什么是随心记
随心记是一个由 AI 生成的网页备忘录,它支持语音录入(可下载)、图文视频记录。最重要的是,它支持离线使用,所有数据都储存在浏览器中,不依赖后端,刷新页面数据也不会丢失!
此外,它还做了语音兼容处理,支持在 iOS 和安卓设备中通过 WebView 嵌套使用。
功能介绍
- 欢迎页
- 信息录入页
支持图文
支持语音录入、播放、下载
数据离线缓存,刷新页面不丢失
仅点击退出会清空数据
技术栈
本项目通过Trae+Claude-3.7生成,纯科技,不夹杂人工代码,项目最终通过掘金MCP一键部署发布。
作品发布&体验地址:https://aicoding.juejin.cn/aicoding/work/7519454663779811366
AI的代码技术栈如下:
- 星空背景:Cnavas实现
- 录音功能:纯原生实现,后为了兼容改用record-core
- 图片/视频功能:Vant-UI
- 数据离线储存:采用IndexDB
AI实践过程
开发工具准备
要使用AI实现这样一个小程序,首先要选取合适的AI编程工具。比如字节的Trae或国外的Cursor都行,由于要使用掘金的MCP一键部署,还是建议使用Trae。
不同的AI模型可能影响代码生成效果,因此,建议使用Claude,编码能力更强。
产品及prompt设计
要使用 AI 生成你想要的项目,首先一定要设计好你的产品,比如它包含哪些模块,有哪些功能,交互逻辑如何等等。因为这些内容,最终都将作为 prompt(AI模型提示词) 投喂给 AI,而 prompt 的详细程度,往往直接决定了你生成的产品质量。
当然,如果你实在不知道怎么设计产品,也可以请教 ChatGPT,比如这样问:
我想通过AI编译器生成一个记事本APP,帮我设计一个详细的prompt
设置自定义智能体
为了让AI开发代码更准确,符合我们的开发、编码习惯,我们可以创建一个智能体,让AI通过我们的预设信息来进行代码生成。
通过AI提问开发项目
一切都准备好后,我们就可以使用AI生成项目了。首先,我们需要让AI帮我们搭建项目框架。
我要创建一个名为“随心记”的APP,使用Vue3+typeScript+less帮我搭建项目框架,不要写入任何内容,等待我的指示
搭建项目的过程中,我们只需要按照提示,运行相应的命令即可:
项目搭建好,我们就可以按照模块,让AI帮我们一步步完善代码。我们以生成星空背景+手机框架为例。
promot:帮我生成星空背景,样式要炫酷,屏幕中间显示一个Ihone的手机框架。
我们可以用过上下文协议,选中某个文件,将代码生成在指定位置:
代码生成后,我们先预览效果,如果效果不达标,我们可以通过不断提问去增加细节,如给背景的星星增加闪烁效果,给背景增加流星之类的。通过不断优化promot,效果会越来越接近我们想要的效果。
给出我通过AI生成的星空背景代码:
<template> <div class=\"media-wrap\"> <canvas class=\"star-canvas\"></canvas> </div> </template> <script setup> import { onMounted } from \'vue\'onMounted(() => { const canvas = document.querySelector(\'.star-canvas\') const ctx = canvas.getContext(\'2d\') function resize() { canvas.width = canvas.offsetWidth canvas.height = canvas.offsetHeight } resize() window.addEventListener(\'resize\', resize) // ⭐ 星星类(星星变大) class Star { constructor() { this.reset() } reset() { this.x = Math.random() * canvas.width this.y = Math.random() * canvas.height this.radius = Math.random() * 2.5 + 0.5 // ⭐ 更大:0.5 ~ 3px this.alpha = Math.random() this.fade = Math.random() * 0.02 this.speed = 0.1 + Math.random() * 0.3 } update() { this.y += this.speed this.alpha += this.fade if (this.alpha <= 0 || this.alpha >= 1) this.fade = -this.fade if (this.y > canvas.height) this.reset() } draw() { ctx.beginPath() ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2) ctx.fillStyle = `rgba(255,255,255,${this.alpha})` ctx.fill() } } // ☄️ 流星类(左下 → 右上) class Meteor { constructor() { this.reset() } reset() { this.x = Math.random() * canvas.width * 0.5 // 左侧 this.y = canvas.height - Math.random() * canvas.height * 0.5 // 下方 this.len = Math.random() * 80 + 100 this.speed = Math.random() * 4 + 6 this.angle = Math.PI / 4 // ↗ 从左下到右上 this.alpha = 1 } update() { this.x += this.speed * Math.cos(this.angle) this.y -= this.speed * Math.sin(this.angle) this.alpha -= 0.01 if (this.alpha <= 0) this.reset() } draw() { const x = this.x const y = this.y const len = this.len ctx.save() ctx.beginPath() ctx.moveTo(x, y) ctx.lineTo( x + len * Math.cos(this.angle), y - len * Math.sin(this.angle) ) ctx.strokeStyle = `rgba(255,255,255,${this.alpha})` ctx.lineWidth = 2 ctx.shadowColor = \'#fff\' ctx.shadowBlur = 10 ctx.stroke() ctx.restore() } } const stars = Array.from({ length: 200 }, () => new Star()) const meteors = Array.from({ length: 3 }, () => new Meteor()) function animate() { ctx.clearRect(0, 0, canvas.width, canvas.height) stars.forEach(star => { star.update() star.draw() }) meteors.forEach(meteor => { meteor.update() meteor.draw() }) requestAnimationFrame(animate) } animate()})</script><style lang=\"less\" scoped>.media-wrap { width: 100%; height: 100vh; overflow: hidden; background: linear-gradient(to bottom, #1b1e3f, #2c3e50, #3b2c59); // 深蓝+紫色 // ✨ 动态渐变星云背景 background: linear-gradient(-45deg, #1b1e3f, #2c3e50, #3b2c59, #1b1e3f); background-size: 400% 400%; animation: galaxyMove 20s ease infinite; .star-canvas { position: absolute; top: 0; left: 0; width: 100%; height: 100%; z-index: 0; } .iphone { width: 380px; height: 85%; border-radius: 30px; position: relative; box-shadow: 0 0 0 10px black; margin: 40px auto; z-index: 1; .screen { width: 100%; height: 100%; border-radius: 30px; margin-top: 5%; overflow: hidden; } .notch { position: absolute; top: 0; left: 50%; transform: translateX(-50%); width: 130px; height: 30px; background: #000; border-bottom-left-radius: 20px; border-bottom-right-radius: 20px; z-index: 2000; } .side-button { position: absolute; width: 4px; background: #666; border-radius: 2px; &.power { height: 40px; right: -6px; top: 120px; } &.volume-up { height: 30px; left: -6px; top: 100px; } &.volume-down { height: 30px; left: -6px; top: 140px; } } }}.title { position: relative; z-index: 2; text-align: left; margin-top: 20px; font-weight: 600; letter-spacing: 1px; text-shadow: 0 2px 10px rgba(0, 0, 0, 0.3); .gradient-text { font-size: 28px; background: linear-gradient(45deg, #f9d423, #ff4e50, #7b4397, #00c6ff); background-size: 300% 300%; -webkit-background-clip: text; -webkit-text-fill-color: transparent; animation: gradient-shift 8s ease infinite, pulse 2s infinite alternate; }}.info { color: #fff; position: fixed; bottom: 10px; right: 10px; cursor: pointer; font-size: 16px;}@keyframes galaxyMove { 0% { background-position: 0% 50%; } 50% { background-position: 100% 50%; } 100% { background-position: 0% 50%; }}@keyframes gradient-shift { 0% { background-position: 0% 50%; } 50% { background-position: 100% 50%; } 100% { background-position: 0% 50%; }}@keyframes pulse { 0% { transform: scale(1); } 100% { transform: scale(1.05); }}</style>
在我用AI 生成的这个项目中,最复杂的部分莫过于录音功能了,通过大约半小时的反复提问与细节优化,终于通过代码实现了语音录入、自定义语音样式、语音播放的功能。
AI的核心实现原理是通过原生录音事件完成的
// 创建音频上下文const AudioContext = window.AudioContext || (window as any).webkitAudioContextaudioContext = new AudioContext()// 创建分析器analyser = audioContext.createAnalyser()analyser.fftSize = 256 // 必须是2的幂次方const bufferLength = analyser.frequencyBinCount // 通常是fftSize的一半dataArray = new Uint8Array(bufferLength)// ...// 请求录音权限navigator.mediaDevices .getUserMedia({ audio: true }) .then((stream) => { resolve(stream) }) .catch((err) => { console.error(\'获取录音权限失败:\', err) Toast(\'获取录音权限失败,请检查权限设置\') reject(err) })// 创建MediaRecorder实例recorder = new MediaRecorder(stream as MediaStream)
但经过实际测试,在IOS的webview中,这种方案兼容性不好,在低版本的IOS中,录音播放异常,这与原生录音方法生成的录音格式有关。
于是,通过搜索,最终使用了record-core实现了录音功能,并让AI帮我重构了代码。
对于数据的离线储存,通过提问AI后,选用了indexDB的储存方案。但在实测中,播放语音时会报错:
于是,通过提问AI继续修复与优化。
在经过不断地测试与AI修复后,功能终于是没有任何问题了。在整体项目生成后,就是利用AI提问不断完善一些小功能
用AI生成项目的过程需要一步步的提问,让AI优化细节,这个过程其实是比较漫长的,但对于不懂技术或者不想自己写代码的同学而言,这都不是什么问题。
项目开发完毕,就可以部署在线上体验了。
项目部署
要发布线上,目前最方便的方式就是在Trae中配置MCP,一键部署。
部署前,我们需要先配置掘金的MCP,在 Trae 或 IDE 的 MCP 配置中,添加以下 JSON 配置:
{ \"mcpServers\": { \"juejin-deploy-mcp\": { \"command\": \"npx\", \"args\": [ \"--registry=https://registry.npmjs.org\", \"-y\", \"@juejin-team/mcp-server@latest\" ], \"env\": { \"JUEJIN_TOKEN\": \"\" } } }}
注意,配置项中的JUEJIN_TOKEN
需要填写你自己的掘金Token,获取地址如下:
https://www.zhihu.com/
点击添加后,你就可以在你的内置智能体中看到到
现在,你只需要切换到这个智能体,给出对应的部署提示即可。
部署成功后,会在对话框看到部署链接,点击后就可以看到效果。
总结
通过本文可以看出,借助 AI 从零开发一个完整的程序已不再是梦想,这让越来越多的人有机会成为创作者。而要用好 AI 开发产品,提示词(prompt)的质量至关重要。令人惊喜的是,优秀的提示词本身也可以由 AI 生成。用 AI 生成 prompt,再用这个 prompt 指导 AI 开发产品,这种“AI 调用 AI”的方式,既魔幻又充满趣味。