前八轮每轮只问一个问题:钱花在哪、有没有人授权、界面上说的话是不是真的。 第八轮修完之后,两个新的审查同时被派出去,一个只查钱,一个只查体验。
结论:这一轮找到的不是新的洞,而是同一批句子在别处又说错了一遍。
一个用 Ollama 的用户打开弹窗,看到:
本页 2 张可翻译图片,翻译会调用模型服务,会产生费用。
页面上写着:
翻译这 2 张图会产生调用费用(缓存命中不计费)
HUD 的按钮写着:
重新翻译失败项(会再次计费)
设置页写着:
页面内后续图片出现时自动继续翻译(会产生费用)
而引导页写的是:
翻译会在你的电脑上完成,图片不会离开本机,也不产生服务商费用。
「会不会花钱」取决于用哪个服务商,而这个扩展一直支持两个世界:
| 服务商 | 图片去哪 | 账单 |
|---|---|---|
| MiniMax / SiliconFlow / OpenRouter / Gemini / OpenAI | 离开这台机器 | 有 |
| Ollama / LM Studio | 留在这台机器 | 没有 |
代码里没有「本地服务商」这个概念——providers/base.ts 里有
KEYLESS_LOCAL_PROVIDERS(不需要 Key 的那一组),但没有「不收钱的
那一组」。于是所有关于钱的句子都写死成「会产生费用」。
引导页是唯一一处做对了的:它单独判断了服务商。设置页没有。
这不是措辞问题。这是扩展唯一一句用来建立信任的话(关于钱的那句),对 一群用户来说,从头到尾都是错的——而这群用户恰好是最不该被吓跑的人。
新增 src/shared/local-providers.ts:本地服务商只有一份名单,
providers/base.ts 也从那里取,两边不会各说各话。
所有关于钱的句子都改成带服务商参数:
describeFee(provider) // 会产生费用 / 不产生服务商费用,图片也不会离开这台机器
describeFeeShort(provider) // 括号里用的短版
describeRequestCount(provider) // 一张图会变成几次请求
describeRetryFailedCost(provider)
describeRetryFee(provider) // 按钮上用的短版
未知的服务商按云端算。 新加一个服务商时,如果它其实不收钱,产品至少 不会在用户面前先说”免费”。
共享的那句话原来写着:
一张图可能变成多次请求:长条图会拆成多片分别请求,模型没读出文字时还会用 OCR 再分批试一次。实际请求次数可能高于图片数。
第三个理由没写:请求超时或失败时会自动重试一次(retryWithBackoff(fn, 2))。
超时是服务商已经收了钱的请求——扩展自己也是这么记的(isAbandonedRequest)。
一张超时的图 therefore 是两次请求,而那句话只说了”可能高于”一个来源。
顺带修掉的:OCR 兜底对本地模型是不做的,那句话却对所有人说本地模型 也会走 OCR。现在它只对云端服务商说。
弹窗里同一个动作是这样的:
重新翻译失败的 3 张 → 弹一个确认框,写明会重新请求多少张
页面上的 HUD 是这样的:
重新翻译失败项(会再次计费) → 点一下就发请求
HUD 是读者在翻章过程中、在旁边、随手会点的那一个。它连张数都没写。
重新翻译失败的 3 张(会再次计费);观察器的规则是「只问从来没问过的图」,而「这张图」是用地址认的:
img.src || `img-${offsetLeft}-${offsetTop}-${width}-${height}`
两种常见的页面做法会让这条记录失效:
<img> 只写 srcset 不写 src——img.src 是空的,于是退化成
“在页面上的位置”,而位置会随着页面回流而变。两种情况下观察器都会认为这是一张新图,再问一次——同一张图,两次请求。
地址和元素都记。元素是这一页上这张图片的稳定身份;地址能抓住”页面把 元素重建了一遍”的情况。两个记录都说”问过了”,才算问过了。
顺带:getImageKey 改用 currentSrc(浏览器真正取的那个地址),
srcset 的图片从此有了一个稳定的地址。
| 地方 | 原来 |
|---|---|
| 商店页面 | 翻译缓存:相同图片不重复请求,节省额度 |
| 设置页 | 翻译过的漫画会缓存在本机,重复打开同一页不会再次调用模型 |
| 审核说明 | 再次翻译同一页预期不再发出任何服务商请求(之前翻译失败的图片没有进缓存,会重新请求) |
审核说明是对的:失败的翻译不进缓存,所以下次打开还会再请求一次。 另外两处没有这个例外,所以它们在承诺一件做不到的事。
失败不能进缓存——把”失败”当成”翻译好了”缓存起来,缓存里就会躺着一个 永远翻不出来的图。所以是改承诺,不是改代码:
翻译缓存:翻译成功的图片不重复请求,节省额度
设置页同一句话也照抄。商店页面的这句话在
docs/store-claims.md 里登记过,改文案要同步改那张表——检查脚本会把
两者对不上这件事变成构建失败。
public/manifest.json 里的描述和代码里的描述钉在一起。border-white/20 压在近黑的背景上是
1.7:1,WCAG 1.4.11 要的是 3:1。弹窗的选择框早先修过同样的问题,
设置页有十个控件没修——包括贴 API Key 的那个框。hybrid-regions)不在缓存键里。 默认路径不是它,设
置页也没有开关,只能改配置文件才会走到。而缓存键的字节不能动——
改了就等于让所有用户的缓存全部失效。这个键要改只能以”版本号”的方式
改,不能加一个新元素。| 测试 | 去掉修复后 |
|---|---|
| 本地服务商的句子不说”产生费用” | 句子变成「会产生费用」 |
| 费用说明里出现 OCR 兜底 | 本地服务商也被告知会走 OCR |
| 页面换了地址的图不再问第二次 | 请求数 1 → 2 |
| 清单里的描述与 manifest 一致 | 描述对不上,测试失败 |
| 弹窗对本地服务商说「次模型调用」 | 弹窗写着「次 API 调用」 |
| 设置页第一行按”有没有预置后端”来写 | 无后端时仍承诺有 |
前八轮的模式是”规则要写在执行它的代码旁边”。这一轮补上的是它的另一半:
一句话要知道自己在对谁说。
「会产生费用」这句话对 MiniMax 的用户是对的,对 Ollama 的用户是错的。 而扩展一直只准备了一种说法,于是要么对一半人说错,要么对另一半人说 对——这次是前者。