Vibe coding 体验

Vibe coding 体验

TL;DR; 最终我部署到了:WebRTC Chat。这是最后的界面效果。

Image

最近 Vibe Coding 概念兴起,我第一次听到的时候,脑海里面第一时间想起的就是「蒸汽波」音乐。Vibe Coding 当然和音乐没有关系,但是这个单词传递的含义和「蒸汽波」给我的感觉很像,一种朦胧的感觉。

不过,最近 AI 圈的概念很多,作为程序员如果没有办法了解概念的话,有一种非常强烈的危机感。我第一时间查了下它的意思:

  • 简单来说就是忘记代码存在,专注想法实现。
  • 使用 AI 工具(可以是任意工具,比如 claude code / codex / cursor 等)实现原型实现,快速迭代想法
  • 人为干预,在生成过程中,人为的对代码生成进行干预。这里的干预指的是对于 tool 工具的确认和修改,同时只通过 prompt 调整最终实现产品实现的目的。 以上是我搜索之后的理解,如果想要知道原文含义可以看 Vibe coding

不过总觉得提出这个概念的人没有办法摆脱利益相关的「嫌疑」,使用过 cursor 等工具都知道,类似 Agent 模式直接修改代码是有次数限制的,Vibe Coding 变相的推广 AI 工具。

以上只是玩笑话,正好,我有一个 side project 想法想要实现,我想尝试下一下在当下:Vibe Coding 能做到什么地步。在尝试之前,我先分享我之前对于 AI 编程工具的想法:

  • AI + 编程对于学习新知识是很有用的,但是如果你已经有非常丰富的经验了,其实提升效率并不是非常明显。
  • AI Prompt 从一种意义上来说就是这个时代的「编程语言」,就像是 JavaScript 那样。

一个想法

这个想法起源于,我处于 Mac 生态环境中,有的时候我想要在多个设备中分享内容,经常遇到的是复制密码,链接之类的。常见的做法就是使用 airdrop,以及 Mac 本身就有共享剪贴板的功能。而且即使是这样,也经常遇到抽风无法找到设备的情况。

所以遇到这种情况,可能大多数人和我一样都用用微信作为中转。但是微信不能同时登录多台设备有的时候也挺恶心的。所以两个 Mac 之前复制东西频繁的退出和登录微信。虽然麻烦了一点,但是比起 airdrop 偶尔抽风,我忍了。

所以,我就想实现一个功能能够在两个设备之间发送和传递内容,当然了,实现方式最好是轻量的,我肯定不会为这个功能专门实现一个服务器做 IM。有点为了醋包饺子的嫌疑。

正好我知道有一个技术是可以实现端到端连接的:WebRTC。但是我只是知道可以这么实现,但是对于这个技术原理是一知半解。按照非 AI 时代的做法,我会按照下面的步骤开始:

  • 先查文档
  • 看有没有最新的 DEMO
  • 阅读代码,实现一个最小功能(只有逻辑没有UI)
  • 然后初始化一个前端项目,写 UI 加功能。
  • 虽然我对美观没有那么高要求,但是还是要求能够看得过去,所以在 CSS 可能会花点时间。

按照之前这个步骤,可能会花点时间,在我的身体健康没有告警之前,可能会在工作日下班之后赶一下进度,再加上周末完成我的项目。 但是现在不行了,只有工作日的半天时间的话,完成这个项目会把战线拖的比较长。就我的经验来看,拖的越久,我对于它的热情和信心是下降的。大概率就搁置了。所以个人项目最好尽早发布。

第一次迭代:tada

我的 AI 工具

  • cursor(订阅了 Pro),模型是 claude-3.5
  • v0(只有免费额度)

(事先声明:cursor 使用并不算是非常资深的用户,MCP 和 cursor rules 用的并不是很多。打算后续再找个时间研究一下。)

Prompt:帮我写一个 webrtc 应用,可以互相传递消息,使用 typescript pnpm

.
├── README.md
├── index.html
├── package.json
├── pnpm-lock.yaml
├── public
├── server.js
├── src
│   ├── components
│   │   └── Chat.tsx
│   ├── hooks
│   │   └── useWebRTC.ts
│   ├── index.css
│   └── main.tsx
├── tsconfig.json
└── vite.config.ts

初始化完成之后开始启动 pnpm dev,不出意外的报错了(这里 cursor 并没有自我尝试使用 dev 进行自我验证)提示缺少 tsconfig.node.json

Prompt:创建 tsconfig.node.json Cursor:我会帮你创建 tsconfig.node.json 文件,这个文件是 Vite 项目所需的 Node.js 相关的 TypeScript 配置文件

Cursor 倒是知道为什么叫它创建 tsconfig.node.json。

此时我开始 Review 它写的代码,由于我并不是很懂 WebRTC 的技术细节,发现建立连接时通过 localId 和 remoteId 建立连接。 Prompt:localId 和 remoteId 是哪里来的? Cursor:回答了我的问题,同时使用 websocket 建立了一个简单的信令服务器 server.js 。 (但是并没有意识到创建了 esm 项目,所以 server.js 作为 cjs 格式是会报错的) Prompt:将 server.js 改为 esm 格式 Cursor:修改完成之后,自己安装依赖,并且启动 server & dev 进行自我验证。

但是这部分明确是需要人为干预(Human-in-the-loop),人为的输入 「Local ID」和 「Remote ID」

Image

这里实现的功能有明显的 Bug:我打开了两个新的 Tab,输入 localId 和 remoteId,但是 connect 按钮无法点击,同时下方的状态提示:「信令通道已开启」一会有提示「信令通道已关闭」。

我摸索好一会(怀疑是输入框建立连接的时机并没有处理的特别好),于是在两个 Tab 先输入 remoteId,然后不断在 localId 删除然后重新输入,总算是可以建立连接,并且建立数据通道。

总结下核心功能:

  • useWebRTC.ts 建立信令和数据 channel
  • server.mjs 简单的信令服务器(可以理解为两个用户交换连接信息,一旦建立连接,后续数据发送就可以不经过这个信令服务器,使用真正的 P2P 方式)

虽然我没有明确的说明要使用 Vite,但是比较惊喜的是还是使用 Vite 作为开发工具,算是比较符合开发喜好。但是可惜的是并没有使用 tailwindcss 和 shadcn 这两个工具,不过也可以理解,因为在没有明确说明情况下,不知道用户的开发习惯也是正常。

第二次迭代:使用 peerjs

第一次迭代虽然有瑕疵,但是已经非常好了,因为我知道如何是我从零开始写这么一个不太熟悉的技术,并不能保证能做的比它好。

但是我对这个项目的语气并不是如此,我希望不希望有任何服务器的代码。不过从目前看起来,信令服务器是必须的,试想一下,两个用户想要发送消息,一定是需要交换身份信息的。

于是我开始搜索:「有没有一种可能在 WebRTC 场景下不要信令服务器」。或者说有没有开源的信令服务器。

此时我使用的搜索工具时:元宝。Deepseek + WebSearch。

(当然,Perplexity 也是另外一种选择)。

经过两三次询问我找到我想要的答案。

Image

Prompt: 使用 peerjs-server 改写项目。 Cursor:将 server 代码和 useWebRTC 改为了使用 peerjs 实现。

从搜索结果来看,server 代码并不需要的,所以我忽略了 server 代码改动,但是 cursor 仅仅更改了 useWebRTC.tsChat.tsx 的前端代码还是使用了旧的 API。

Prompt:@Chat.tsx 更新。

让我看看这次迭代的效果:

Image

前端界面不变,但是第一次迭代时候前端奇怪的连接 Bug 消失了。useWebRTC.ts 代码从 290 行减少到了 120 行左右。

第三次迭代:v0 设计

现在有很多写前端页面的工具,比如 v0,new.website 等等(以及我相对比较期待的 Variant)。理论上使用 cursor 也可以写前端界面就像是上面那样。

但是我更想要的是:图片转代码。

第一次尝试,直接在 cursor 中上传图片,我从 dribble 找了一个看起来相对舒服且简单的设计效果。

Image

来源:https://dribbble.com/shots/25687049-Agent-Chat-Interface 这里就不贴效果了,属于特别差那种。

于是我在 https://v0.dev 上重新尝试还原效果,v0 并不能一次性 1:1 还原效果。经过9次对话迭代之后

Image

虽然效果并不算太还原,但是在布局上已经是很 “类似” 了,我在右上角添加了一个二维码扫码功能,这也是后面我要添加的功能。期望像是微信那样扫码建立连接。

现在问题来了,v0 和 cursor 明显是两套项目,我怎么融合呢?如果之前可能我还需要花点时间,现在我选择相信 AI。我先下载 v0 的项目到本地(chat-clone),放到同一个文件夹:

.
├── README.md
├── SETUP.md
+ ├── chat-clone
├── components.json
├── dist
├── index.html
├── lib
├── node_modules
├── package.json
├── pnpm-lock.yaml
├── public
├── server.mjs
├── src
├── tailwind.config.mjs
├── tsconfig.json
├── tsconfig.node.json
└── vite.config.ts

然后告诉 AI 尝试合并两个文件夹。

Prompt:@src 参考 @chat-clone 的设计,不懂可以使用 use context7 进行询问。 context7 是一个文档 MCP 工具 Up-to-date documentation for LLMs and AI code editors

Image

除了参考 v0 的设计之外,还加上原先的代码逻辑,比如连接状态。效果还算是满意。

在 「第三次迭代」 之前,我还尝试在项目添加了 shadcn 和 tailwindcss 配置,这个过程非常痛苦,原因在于:

  • 第一次添加了 compoents.json(shadcn),tailwindcss 等配置文件
  • 但是第一次总是缺少一些文件,第二次让它补上文件之后,使用的 tailwindcss 版本和第一次并不是同一个,不同版本的 tailwindcss 配置方式还是有些不同的。 由于过程反反复复让人闹心,我直接选择查 shadcn 和 tailwindcss 文档手动开始配置,算是违背了 Vibe Coding 的初心。

第四次迭代:添加二维码扫描

Prompt:popover 部分弹出显示的是二维码,同时页面上添加唤起二维码扫描 Prompt:@qr-scanner.tsx 添加获取 camera 权限功能

此时 camera 功能一直不生效,这时候我已经有经验了,先去搜索「为什么获取获取权限之后,camera 不生效」。原来由于浏览器安全条件限制,需要 HTTPS 才能使用。

Prompt:@vite.config.ts 加上 https 功能

import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import tailwindcss from "@tailwindcss/vite"
import basicSsl from '@vitejs/plugin-basic-ssl'
import path from 'path'

export default defineConfig({
  plugins: [react(), tailwindcss(), basicSsl()],
  server: {
    port: 3000,
    host: true,
  },
  resolve: {
    alias: {
      "@": path.resolve(__dirname, "./src"),
    },
  },
}) 

最后我们看看这次迭代的效果

Image

扫码需要在手机上访问打开,这部分我就不贴图片了。后续我又添加了 ID 随机生成不需要输入的功能,这样一开始就可以分配一个默认的 localId。

随机生成 ID 之后,我一定会遇到无法连接到信令服务器的错误,删除之后重新输入又可以建立连接了。

Prompt:第一次「应用一打开第一次会出现与信令服务器断开连接,在输入框重新输入就好了,请修复这个问题」这么尝试修复这个问题。

结论是并没有修复这个问题。cursor 修复方式是优化建立连接的初始化时机。

useEffect(() => {
  // ...init
}, [localId])

后来我重新看了下日志(浏览器控制台)。发现「控制台输出:Initializing peer with ID: localpeer-494ce0ad-d03d-4f65-bbbd-a9fdce9b496f 之后立马输出了 Cleaning up peer connection」

这时候我大概猜到了是重新渲染的问题,看了下代码,项目中开启了 React.StrictMode,导致了同一个 ID 重复建立了两次连接,由于用户 ID 唯一的原则,所以就会出现以上错误。本着 Vibe Coding 的原则,我给了错误的日志输出,看看能不能修复这个问题。发现修复的方式还是第一次类似,并不能本质上解决。

于是我开始手动介入,移除了 StrictMode,BUG 消失。

第五次迭代:部署

第五次迭代之前,我还手动介入了几次,调整了下样式。因为我觉得描述我想要的 UI 效果过于费劲。

Prompt: 增加部署到 Netlify 功能

这里开启默认奇妙的修改了 package.json 和 vite.config.ts 文件,修改的改动我都 reject 了。最后使用它的 netlify.toml 文件

[build]
  command = "pnpm build"
  publish = "dist"

[build.environment]
  NODE_VERSION = "18"
  NPM_FLAGS = "--version"
  PNPM_FLAGS = "--version"

[functions]
  node_bundler = "esbuild"

# Ensure pnpm is installed and used
[[plugins]]
  package = "@netlify/plugin-pnpm"

[dev]
  command = "pnpm dev"
  port = 5173
  targetPort = 5173

部署之后发现,@netlify/plugin-pnpm 并没有这个包。于是我让它 @https://www.netlify.com/blog/how-to-use-pnpm-with-netlify-build/ 参考这个文章进行修改。

结果还是不太正确。于是我还是进行了手动介入。改为了

[build]
  command = "pnpm run build"
  publish = "dist"

[build.environment]
  NPM_FLAGS = "--version"
  NODE_VERSION = "20"

[dev]
  command = "pnpm dev"
  port = 5173
  targetPort = 5173

[[redirects]]
  from = "/*"
  to = "/index.html"
  status = 200 

最终我部署到了:WebRTC Chat。这是最后的界面效果。

Image

结论

总体结果我非常满意。在我不太熟悉 WebRTC 情况下,加速了个人项目开发部署上线。但是在还原设计效果上还是有不太令人满意。对于 cursor 这部分订阅十分划算。如果未来有好的图转代码的 AI 工具,我还是为它付费。

对于我这种偶尔脑子里面的想法比较多,但是 996 导致时间又不是很多的选手,AI 工具就是最好的效率工具。 #Blogs