Claude3.5 打造漂亮的动态竞速图工具

时不时在网上看到一些漂亮的竞速图页面,展示数据随时间的变化情况,看着很酷。然后查了下有现成的 js 库可以生成这种图表,不过没找到一个合适的工具,可以将数据上传后,自动生成竞速图。

于是想着用 Claude3.5 实现一个,不过这个过程并不顺利,中间踩了不少坑。从 7 月份都开始着手,中间断断续续尝试了多次,直到最近才算实现了一个简单的版本。本文记录下用 Calude3.5 实现这个工具的过程,记录其中踩过的坑。最终的效果可以在这里体验,欢迎大家留言讨论。

失败尝试

最开始的时候,想着比较简单,就先去找了下目前哪些库支持动态竞速图。

决定用 于是就直接提示:

帮我用 react 实现一个网页工具,可以根据用户传的 json 数据,生成动态竞速表。

生成了一个粗糙的代码,需要特定的数据才能跑起来。并且效果也很差,就暂时搁置了。

粗糙的竞速图

继续尝试

最近用 cursor 比较舒服,就想着再来试试。原来的代码能解析 json,然后也能跑起来,这次想着一次性支持多点,能够支持传入 excel 或者 csv 文件,然后数据格式也可以自定义这样子。

打开原来的代码,选定代码后,直接提示:

我想支持传入 excel 或者 csv 文件,然后生成竞速直方图。 用 ReactECharts 实现,完善下面的代码。

然后 claude3.5 让我导入 excel 库,这里踩了坑,先让我从 xlsx 导入,结果安装的是 xlsx-js-style,货不对版。重新提示修好这些小问题后,让它生成了一份示例数据,然后想着来拿示例数据测试下。示例数据虽然短,不过内容倒是不错:

年份 类别 数值
2015 苹果 100
2015 香蕉 80
2015 橙子 60
2015 葡萄 40
2015 芒果 20
2016 苹果 120
2016 香蕉 90
2016 橙子 70
2016 葡萄 50
2016 芒果 30
2017 苹果 130
2017 香蕉 110
2017 橙子 90
2017 葡萄 70
2017 芒果 50

这里第一列是时间,第二列是类别,第三列是值,每列名字可以自定义,传入文件后希望能根据时间,生成不同类别的数值动态竞速图。搞好一个版本后,发现传入csv 和 excel 还是不行,必须先传入 excel,再传一个 json 才能生成一个简单竞速图。先不说这个过程有多奇怪,这里竞速图的纵坐标也没有类别的名字。

为了让它能在纵坐标支持名字,花了好长时间,提示了几次也不太行。中间改动还导致连动态图也出不来了,然后加了日志,发现导入的数据解析有问题。让 Claude3.5 反复分析了好几轮,始终没有找到解决方法,看来 Claude3.5 还是不太适合做这种复杂的分析

动态竞速图样例

算了,不再继续尝试,不如看看官方示例是怎么做的。毕竟只有自己懂怎么写,才能指导 AI 写出来。官方示例代码足够简单,样式也很好看,如下:

官方的竞速直方图示例

代码看起来也不是很复杂,导入数据后,指定下动态竞速图的配置,然后很简单就可以生成一个动态竞速图。把这部分提供给 Claude3.5 解读,让它作为参考代码,它肯定能理解。

既然生成动态竞速图的逻辑已经知道,那么剩下的就是处理前端页面,让用户可以上传文件,然后解析文件,生成动态竞速图。这里让 AI 直接生成这么多,还是有复杂度,人工拆分任务,让 Claude3.5 一点点完成呢

拆分问题!

这里的任务其实挺好拆分的,先是上传和解析 Json 文件相关部分,接着可以加一个文件预览,这样方面使用者理解数据。有数据的情况下,再结合前面的官方示例代码,就可以生成动态竞速图了。这些都搞定后,可以在一个能跑的版本上慢慢优化迭代,比如增加导出 Gif 功能,或者支持不同颜色,支持添加标题等。后面如果想支持 Excel 或者 csv 也比较简单,只用写处理文件部分就好,其他都能复用。

文件上传、预览

这里先看第一部分,直接让 AI 生成一个上传组件,可以传 json 文件。同时为了不限制死文件格式,考虑让使用者选择某列来作为图表的数据源。其实这里需要的数据主要有 3 列,时间、类别和值。再传入 Json 文件后,直接解析里面的列名,然后返回选择框就行。

直接描述有点费劲,为了让 Claude3.5 更好理解,我直接给它画了个草图,如下:

上传组件草图

让它参考这个实现。不过后来想了下,还是让页面整体布局和站点其他的保持一直,于是把设置相关的放到最右边去。Claude 实现的版本并不理想,有一些小问题,不过可以接着微调提示:

不对,你这里没有读上传的 json 文件啊。 要读文件内容,判断是否合法的 json,如果是合法的 json 则输出预览。 否则输出一个弹窗,告诉 json 解析错误。

这里的弹窗新建一个基础组建,有关闭按钮,在屏幕中央输出,上面有遮罩

这次添加了一个 Modal 遮罩组件,并且不止文件解析错误会输出。在没有选择数据源的时候,也会输出一个弹窗,这就是 Claude 带来的意外惊喜了。预览部分上面提示词没有说很详细,Claude 用一个表格来实现,有点小问题,不过很容易就能改对了。

竞速图生成部分

上传部分搞定了,也能预览数据了,接着就是生成竞速图了。提示词也很简单:

继续完善这里的代码,现在选择好列之后,点击生成图形,就要根据传入的 json,生成竞速图。

竞速图在预览的下方部分。

竞速图可以参考这里的: const updateFrequency = 2000; ...

这里从官方复制了完整的竞速图代码,上面不列出了。这次 Claude3.5 生成的代码很不错,终于能复现官网的例子了。不过还有些小问题,直接提出来给 Claude3.5:

这里两个问题:

  1. 第一次点击生成图形的时候,先出来了一个竖条,卡在那里,等了下才出现动态库;
  2. 后续点击生成图形的时候,之前的没完成的还在继续,导致会两个叠加在一起

第一个问题一次性完美解决,在设置初始 chartOption 时就包含第一年的数据,这样图表一开始就会显示有意义的数据。第二个问题,开始的提示词可能不太清晰,导致第二次点击后数据是慢慢回到开始,接着补充了详细提示,问题也很快解决。

至此,一个能跑的版本出来啦!

导出 GIF 的难点

为了方便导出 GIF,接着先简单提示:

这里可以支持导出竞速动态图吗?整个竞速完整过程生成 gif 或者 webp 动态图,支持下载

给出的第一个方案是使用 html2canvas 库来捕获图表的每一帧,然后使用 gif.js 库将这些帧合成为 GIF 动画。第一个版本不行,点了生成 gif 后一直卡在正在生成 GIF..。于是接着完善提示词:

我想要的是:

  1. 生成 gif 的时候,不影响现在站点上的竞速图;现在会有两份竞速图不断覆盖,这样不对
  2. 生成 gif 在后台,不要等真的每秒这样的过,要支持快速生成完整的竞速图。 生成完整后就弹出保存窗口,可以保存 gif

中间反复生成了几个版本,都没能解决。于是尝试让 AI 分析下这里的原因,然后提供日志来排查下。接着 Claude 在代码中添加了不少日志,生成的时候如下:

竞速图生成完成

这里一直卡在 Gif 渲染了。把日志也给 Claude3.5 看了下,但是还是没定位到问题。这里让 Claude3.5 反思下有哪些可能导致这里的问题,给出了一堆:

  1. 图像加载:gif.js 需要完全加载的图像才能正确渲染。
  2. canvas 大小:确保 canvas 的尺寸正确设置。
  3. 帧的添加方式:确保正确地将帧添加到 GIF 对象中。
  4. 内存使用:大量帧可能导致内存问题。
  5. 异步操作:确保所有帧都已添加后才调用 render() 方法。

逐个排除了下,还是有问题。去看了下 gif.js 的官方文档,依旧没什么头绪。又耐着心去看下 AI 生成的代码,发现在创建 jif 对象的时候,是这样的:

const gif = new GIF({
    workers: 4,
    quality: 10,
    width: canvasWidth,
    height: canvasHeight,
    workerScript: '/path/to/gif.worker.js' // 确保正确设置 worker 脚本路径
});

问了下 Claude3.5 这个文件的作用,以及如何设置。gif.worker.js 文件包含了 GIF 编码的核心逻辑。它在 Web Worker 中运行,允许 GIF 编码过程在后台进行,不会阻塞主线程。在 nextjs 项目中安装 gif.js 库后,可以在 node_modules 中找到 gif.worker.js 文件,将它复制到 public 目录下,然后设置为 /gif.worker.js

看起来很对,但跑了还是不行!又尝试了几轮对话,始终卡在渲染 GIF 那里。Claude3.5 似乎钻进了牛角尖,接着重新开一个对话,换 GPTo1 来试试。提示词也很少:

已生成并添加第 80 帧 content.js:241 已生成并添加第 81 帧
content.js:224 所有帧生成完毕,开始渲染 GIF
content.js:226 调用 gif.render()
content.js:250 GIF 渲染开始
content.js:252 GIF 渲染进度: 0.00%

这里渲染 gjf 一直卡在渲染进度 0 ,似乎没有开始。帮我解决

哈哈,这里甚至拼错了 gif 为 gjf,但 GPTo1 还是很快理解了,并给出了方向:

看起来您的 GIF 渲染过程卡在了进度 0.00%,这通常与 gif.worker.js 的加载问题有关。以下是一些可能的原因和解决方法:

  1. 确认 gif.worker.js 的路径
    确保 gif.worker.js 文件位于正确的位置,并且路径 /gif.worker.js 是可以访问的。如果您将 gif.worker.js 放在 public 文件夹中,确保它可以通过浏览器访问。例如,访问 http://localhost:3000/gif.worker.js 应该能看到该文件内容。
  2. 检查浏览器控制台错误
    打开浏览器的开发者工具,查看是否有关于 gif.worker.js 加载的错误。如果有 404 错误,说明路径不正确;如果有跨域问题,可能需要调整服务器配置。

试了下,发现访问的时候路径被重定向了。忽然想起来,当时为了支持旧的没有带语言版本的链接时,在 middleware.js 中加了自动重定向,会在链接中增加语言。比如访问 https://gallery.selfboot.cn/tools/chartrace 会自动重定向到 https://gallery.selfboot.cn/en/tools/chartrace/ 。这里修复比较简单,去掉 gif.worker.js 的自动重定向,再次尝试导出就可以了。

其他细节优化

大的功能点都完成了,下面就是用 cursor 来优化下细节。不得不说,单纯完成小的细节任务,claude 3.5 正确率还是很高的。比如:

  1. 修改生成动态 GIF 图的背景颜色;
  2. 下载文件的名字改为上传文件名,后缀是 gif;
  3. 把导出 gif 按钮放到右边设置页,导出的时候支持进度条展示进度;
  4. 支持自定义输入标题;
  5. 每条数据的颜色可以随机生成,然后保证颜色搭配合理;
  6. 修改代码支持多语言,生成翻译文件。

这些任务要是自己来写的话,虽然也能实现,但还是要花不少时间的。用 Claude3.5 完成这些细节任务,就轻松很多了。

Cursor Claude3.5 缺陷

在使用的过程中,发现 cursor 还是有不少问题的。生成代码后,点击 Apply,发现变更的 diff 中,有时候会把已经写好的代码删掉,用注释来替换了。所以对于变更,还是不能无脑 Apply,需要人工检查下才行。

此外写的过程中,比如你随手改了一个地方。后续 AI 生成的时候,可能又给你改回去。比如下图中,我已经把这里的时间改成了 500ms,后续每次 Apply 的时候,都会改回去 2000:

AI 错误修改改动的代码

猜测这里可能是 cursor 选择代码版本的时候,并没有用当前编辑器中的版本。而是自己在上下文维护了一个代码版本,然后基于维护的版本生成新的代码。之后在 diff 的时候,用了老的版本来覆盖编辑器改动过的版本。

还有一个很不爽的地方就是,Claude3.5 在功能改动的时候,经常忘记删掉多余的代码。比如一个功能已经改变了实现,经常只是增加代码,没有删除掉不用的代码。比如下图,这里渲染的时候没有用到一些状态,但是还在。只能提示它去删掉,好在稍微提示下,它就立马知道怎么删除了。

AI 生成代码的时候没有删除不用的

使用过程反思

找到和 Claude3.5 的正确沟通方法后,从零写一个工具还是挺快的。只用专注于整体的实现思路,然后尽量把任务拆分,每一步 AI 基本完成的还不错。遇到有 Bug 的地方,稍微看看代码,然后也基本也能解决。

有时候单个模型可能钻牛角尖,可以试着换个模型。这次生成 GIF 的时候,用 Claude3.5 排查了半天也没能解决,后来切换到 GPTo1,重新开了个会话,直接就给了很好的排查方向,一下子也就解决问题了。

再分享一个个人小技巧,有的对话长了后,AI 每次提问会带很多上下文,回答的准确度会下降不少。这时候重新开一个新的对话,效果可能会好很多。

你想要什么工具吗?试试用 cursor 来实现吧。