凌晨十二点半,我盯着刚上线的 Polly Chat 2.0,突然冒出一个念头:
"能不能让它看懂图片?"
三十分钟后,这个功能上线了。
起因:一个简单的问题
Polly Chat 2.0 刚完成零后端重构,用 Cloudflare Worker 代理直连 CopilotX(我的 Copilot API 代理),体验非常丝滑。但作为技术博客的门面,纯文本对话总觉得差点意思。
Claude 本身支持 Vision,CopilotX 理论上也应该支持——但我从没测试过。
技术验证:一个红色方块
与其猜测,不如直接测。我写了个 30 行的测试脚本:
# 创建一个 50x50 的红色方块
img = Image.new('RGB', (50, 50), color='red')
buffer = io.BytesIO()
img.save(buffer, format='PNG')
img_base64 = base64.b64encode(buffer.getvalue()).decode('utf-8')
# 发送给 CopilotX (Anthropic 格式)
payload = {
"model": "claude-sonnet-4-20250514",
"messages": [{
"role": "user",
"content": [
{"type": "image", "source": {"type": "base64", "media_type": "image/png", "data": img_base64}},
{"type": "text", "text": "What color is this image? Answer in one word."}
]
}]
}
运行结果:
Status: 200
AI Response: Red
✅ Vision support works!
太好了,CopilotX 原生支持 Vision,不需要改后端!
交互设计:按钮?不,Ctrl+V 更优雅
接下来是前端。传统做法是加一个上传按钮,但我想到了更自然的交互:
| 方案 | 参考产品 | 优点 | 缺点 |
|---|---|---|---|
| 左侧按钮 | 直观 | 占空间 | |
| 右侧按钮 | ChatGPT | 紧凑 | 按钮区拥挤 |
| Ctrl+V 粘贴 | Slack/Discord | 零 UI 改动,符合直觉 | 需要提示 |
最终选择了粘贴方案。用户截图后直接 Ctrl+V,图片出现在输入框上方:
┌──────────────────────────────────────────┐
│ ┌─────┐ │
│ │ 🖼️ │ ❌ │
│ └─────┘ │
│ ┌────────────────────────────────────┐ │
│ │ 这张图是什么? [✈️] │ │
│ └────────────────────────────────────┘ │
└──────────────────────────────────────────┘
实现核心代码:
userInput.addEventListener('paste', (e) => {
const items = e.clipboardData?.items;
if (!items) return;
for (let item of items) {
if (item.type.startsWith('image/')) {
e.preventDefault();
const file = item.getAsFile();
processImage(file); // 压缩 + 预览 + 存储
break;
}
}
});
踩坑:413 Payload Too Large
第一次测试发图,直接报错:
Oops, something went wrong: HTTP 413
原因:Cloudflare Worker 免费版请求体限制 100KB,而一张普通截图的 base64 编码轻松超过 1MB。
解决方案:前端压缩。Canvas 大法好:
async function compressImage(file, maxKB = 50, maxDim = 800) {
return new Promise((resolve) => {
const img = new Image();
img.onload = () => {
// 缩放到最大 800px
let { width, height } = img;
if (width > maxDim || height > maxDim) {
const ratio = Math.min(maxDim/width, maxDim/height);
width *= ratio;
height *= ratio;
}
// Canvas 压缩 + JPEG 降质
const canvas = document.createElement('canvas');
canvas.width = width;
canvas.height = height;
const ctx = canvas.getContext('2d');
ctx.drawImage(img, 0, 0, width, height);
// 循环降低质量直到 < 50KB
let quality = 0.8;
let base64;
do {
base64 = canvas.toDataURL('image/jpeg', quality);
quality -= 0.1;
} while (base64.length > maxKB * 1024 * 1.37 && quality > 0.1);
resolve(base64);
};
img.src = URL.createObjectURL(file);
});
}
再次测试:成功!
UI 打磨:极简滚动条
功能完成后,我注意到聊天内容多了后,右侧的滚动条有点突兀——和博客的简约风格不搭。
解决方案:hover 时才显示的超细滚动条:
#chat-box {
scrollbar-width: thin;
scrollbar-color: transparent transparent;
}
#chat-box:hover {
scrollbar-color: rgba(0, 0, 0, 0.15) transparent;
}
#chat-box::-webkit-scrollbar {
width: 6px;
}
#chat-box::-webkit-scrollbar-thumb {
background: transparent;
transition: background 0.3s ease;
}
#chat-box:hover::-webkit-scrollbar-thumb {
background: rgba(0, 0, 0, 0.15);
}
效果:平时几乎不可见,滑动时优雅显现。
总结:30 分钟的愉悦开发
| 阶段 | 耗时 | 产出 |
|---|---|---|
| 技术验证 | 5 min | 确认 CopilotX Vision 可用 |
| 交互设计 | 5 min | 选定 Ctrl+V 方案 |
| 前端实现 | 10 min | paste 监听 + 预览 UI |
| 踩坑修复 | 8 min | 图片压缩 |
| UI 打磨 | 2 min | 极简滚动条 |
这次开发体验极度愉悦,原因有三:
- 验证优先:先确认后端能力,避免无用功
- 设计简约:Ctrl+V 比按钮更优雅,代码也更少
- 渐进增强:先跑通核心流程,再解决边缘问题
最终效果如封面图所示——截图粘贴,AI 秒懂。
现在,你可以在博客首页试试:截个图,Ctrl+V,问问 Polly 看到了什么。

