💥第9天:用Sonnet 3.5打造专属Agent工具,你也能做到!💥
3 个月前
100天代理工程师挑战第9天:为Agent定制Bubble工具,Sonnet 3.5 vs GPT-4o
低代码解决方案非常棒,你只需要打开浏览器,创建项目,然后开始构建你的应用程序,不需要安装、升级或维护任何包。你甚至可以在全新的电脑或别人的笔记本上启动项目,唯一需要的只是一个浏览器和网络连接。但有时候,你可能需要一个无代码或低代码工具无法直接支持的解决方案。没问题,你总是可以通过自定义代码来扩展它,但有一个重要的注意事项:在集成自定义代码时,你需要确保代码与低代码界面之间的连接保持顺畅。在继续之前,让我们先回顾一下我的日常任务。
我的日常任务
1.锻炼:20个俯卧撑 — 20个俯卧撑轻松完成,感觉越来越简单了 :)
2.七小时睡眠 — 晚睡是个问题,明天得改掉这个习惯。
3.低代码开发:3小时 — 使用Bubble、Langflow、FlutterFlow并进行定制
4.AI助手:排队中
5.PAIC:排队中
6.数据科学:排队中
如果你想了解这些任务的具体内容,请阅读挑战的引言部分。
无代码网页应用工具与代理AI有什么关系?
要构建代理,我们需要一些工具来帮助我们,所以我从构建这些工具开始。我使用低代码来制作这些工具,以便快速构建并随时进行测试,而无需浪费时间在部署或维护上。
使用FLUX.1 Fill进行图像修复与扩展
图片来源:Black Forest Labs
FLUX.1 Fill 提供了强大的图像修复功能,比Ideogram 2.0等工具以及开源选项如AlimamaCreative的FLUX-Controlnet-Inpainting更出色。它使图像编辑变得简单,并且能够自然地融合修改。它还支持图像扩展(outpainting),因此你可以将图像扩展到原始边缘之外。
图片来源:Black Forest Labs
让我们从图像修复功能开始,图像扩展功能改天再讨论。以下是一个示例cUrl代码,它将帮助我们把这个图像修复功能集成到Bubble应用中。
curl https://api.bfl.ml/v1/flux-pro-1.0-fill
--request POST
--header 'Content-Type: application/json'
--header 'X-Key: YOUR_SECRET_TOKEN'
--data '{
"image": "",
"mask": "",
"prompt": "ein fantastisches bild",
"steps": 50,
"prompt_upsampling": false,
"seed": 1,
"guidance": 60,
"output_format": "jpeg",
"safety_tolerance": 2
}'
如你所见,我们需要一张图片和另一张标记了需要修改或替换部分的图片,第二张图片应作为遮罩发送。两张图片都需要进行Base64编码。
在Bubble中使用自定义代码进行图像AI编辑
在Bubble中,有一些画布插件,甚至有一个集成了fabric.js的插件,但我需要让它更用户友好,并保持现有的UI。我的想法是上传图片,然后允许用户标记需要替换的部分。我通过HTML元素和自定义的Vanilla JS代码实现了这一点,但最重要的是,只在必要时使用自定义代码,以保持脚本与Bubble之间的连接,并且工作流也应由Bubble管理。
<div id="canvas-container" style="position: relative;">
<canvas id="drawingCanvas" style="border:1px solid #d3d3d3; display: none;"></canvas>
</div>
<script>
(function() {
const canvas = document.getElementById('drawingCanvas');
const ctx = canvas.getContext('2d');
const img = new Image();
img.crossOrigin = "Anonymous";
let isDrawing = false;
let path = [];
// 更新画布图片URL的函数
function loadImage(url) {
img.src = url;
img.onload = function() {
canvas.width = img.width;
canvas.height = img.height;
canvas.style.display = "block";
ctx.drawImage(img, 0, 0);
addSaveButton();
};
}
let dynamicImageUrl = "YOUR_DYNAMIC_IMAGE_URL";
function updateImageUrl(newUrl) {
if (newUrl && newUrl !== dynamicImageUrl) {
dynamicImageUrl = newUrl;
loadImage(dynamicImageUrl);
}
}
canvas.addEventListener('mousedown', (e) => {
isDrawing = true;
path = [{ x: e.offsetX, y: e.offsetY }];
});
canvas.addEventListener('mousemove', (e) => {
if (isDrawing) {
const point = { x: e.offsetX, y: e.offsetY };
path.push(point);
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(img, 0, 0);
ctx.beginPath();
ctx.moveTo(path[0].x, path[0].y);
path.forEach(p => ctx.lineTo(p.x, p.y));
// 在画布上提供视觉反馈
ctx.lineWidth = 20;
ctx.strokeStyle = 'rgba(255, 0, 0, 0.3)';
ctx.lineJoin = 'round';
ctx.lineCap = 'round';
ctx.stroke();
}
});
canvas.addEventListener('mouseup', () => {
isDrawing = false;
});
window.saveMask = function() {
const maskCanvas = document.createElement("canvas");
const maskCtx = maskCanvas.getContext("2d");
maskCanvas.width = canvas.width;
maskCanvas.height = canvas.height;
// 将整个背景设为黑色
maskCtx.fillStyle = "black";
maskCtx.fillRect(0, 0, maskCanvas.width, maskCanvas.height);
// 用纯白色绘制选中的路径
maskCtx.beginPath();
maskCtx.moveTo(path[0].x, path[0].y);
path.forEach(p => maskCtx.lineTo(p.x, p.y));
maskCtx.lineWidth = 20;
maskCtx.lineJoin = 'round';
maskCtx.lineCap = 'round';
maskCtx.strokeStyle = "white"; // 选中区域用纯白色
maskCtx.stroke();
const maskData = maskCanvas.toDataURL("image/png").replace(/^data:image/png;base64,/, "");
if (typeof window.bubble_fn_saveMaskData === "function") {
window.bubble_fn_saveMaskData("");
}
setTimeout(() => {
if (typeof window.bubble_fn_saveMaskData === "function") {
window.bubble_fn_saveMaskData(maskData);
} else {
console.error("JavaScript to Bubble function is not defined.");
}
}, 50);
};
function addSaveButton() {
const existingButton = document.getElementById('saveMaskButton');
if (existingButton) return;
const saveButton = document.createElement('button');
saveButton.id = 'saveMaskButton';
saveButton.innerHTML = '保存遮罩';
saveButton.style.position = 'absolute';
saveButton.style.top = '10px';
saveButton.style.left = '10px';
saveButton.style.padding = '10px';
saveButton.style.backgroundColor = '#4CAF50';
saveButton.style.color = 'white';
saveButton.style.border = 'none';
saveButton.style.cursor = 'pointer';
saveButton.onclick = saveMask;
const canvasContainer = document.getElementById('canvas-container');
canvasContainer.appendChild(saveButton);
}
window.updateImageUrl = updateImageUrl;
})();
</script>
我使用了Toolbox插件及其JavaScript到Bubble的元素。它运行得很好,令人惊讶的是,通过如此简短的脚本就能添加自定义功能。
Claude Sonnet 3.5 vs OpenAI GPT-4o
我尝试了Sonnet和GPT-4o来帮助我编写脚本。Claude Sonnet 立即处理了绘图功能,而GPT-4o则开始添加一些奇怪的方块,而不是简单的绘图。但有趣的是,GPT-4o更了解如何将脚本连接到Bubble的基础设施。有时候,使用两个模型进行开发确实是有意义的。
FluxAI 中文
© 2025. All Rights Reserved