From 57e98165127835c1e0f1ed97dff5313b3827832e Mon Sep 17 00:00:00 2001
From: midcodehub <408124546@qq.com>
Date: Wed, 8 Jul 2026 11:47:31 +0800
Subject: [PATCH 01/15] feat: add batch processing system, GPU infrastructure,
and pricing page while updating asset versions and project configuration
---
.beamignore | 30 +
.claude/logs/tool-audit.jsonl | 141 +
.claude/settings.local.json | 17 +-
.gitignore | 11 +
Dockerfile | 26 +-
Dockerfile.openvino | 117 +
account/credits/index.html | 182 +
alternatives/remove-bg/index.html | 2 +-
app.js | 91 +-
batch/batch.js | 310 ++
batch/index.html | 430 ++
bn/id-photo-maker/index.html | 2 +-
bn/index.html | 1797 +++++----
bn/jpg-to-transparent-png/index.html | 2 +-
bn/old-photo-restoration/index.html | 2 +-
bn/png-to-jpg-white-background/index.html | 2 +-
bn/portrait-background-remover/index.html | 2 +-
.../index.html | 2 +-
bn/watermark-remover/index.html | 2 +-
feedback.js | 2 +-
fil/id-photo-maker/index.html | 2 +-
fil/index.html | 1797 +++++----
fil/jpg-to-transparent-png/index.html | 2 +-
fil/old-photo-restoration/index.html | 2 +-
fil/png-to-jpg-white-background/index.html | 2 +-
fil/portrait-background-remover/index.html | 2 +-
.../index.html | 2 +-
fil/watermark-remover/index.html | 2 +-
gpu-scheduler/package-lock.json | 1618 ++++++++
gpu-scheduler/package.json | 18 +
gpu-scheduler/src/index.ts | 215 +
gpu-scheduler/tsconfig.json | 21 +
gpu-scheduler/wrangler.toml | 25 +
hi/id-photo-maker/index.html | 2 +-
hi/index.html | 1797 +++++----
hi/jpg-to-transparent-png/index.html | 2 +-
hi/old-photo-restoration/index.html | 2 +-
hi/png-to-jpg-white-background/index.html | 2 +-
hi/portrait-background-remover/index.html | 2 +-
.../index.html | 2 +-
hi/watermark-remover/index.html | 2 +-
how-to-remove-background/gimp/index.html | 2 +-
how-to-remove-background/index.html | 2 +-
.../powerpoint/index.html | 2 +-
id-photo-maker/id-photo.js | 4 +-
id-photo-maker/index.html | 2 +-
id/id-photo-maker/index.html | 2 +-
id/index.html | 1797 +++++----
id/jpg-to-transparent-png/index.html | 2 +-
id/old-photo-restoration/index.html | 2 +-
id/png-to-jpg-white-background/index.html | 2 +-
id/portrait-background-remover/index.html | 2 +-
.../index.html | 2 +-
id/watermark-remover/index.html | 2 +-
index.html | 1950 ++++-----
jpg-to-transparent-png/index.html | 2 +-
language-menu.js | 2 +-
main.py | 287 +-
miaocut-seo-design-doc.md | 3521 -----------------
old-photo-restoration-design.md | 68 -
old-photo-restoration/index.html | 2 +-
old-photo-restoration/old-photo.js | 15 +-
output.css | 2 +-
png-to-jpg-white-background/index.html | 2 +-
png-to-jpg-white-background/png-to-jpg.js | 10 +-
portrait-background-remover/index.html | 2 +-
pricing/index.html | 129 +
privacy/index.html | 2 +-
pro-account.js | 177 +
pro-gateway/README.md | 99 +
pro-gateway/db/schema-batch.sql | 230 ++
pro-gateway/db/schema.sql | 286 ++
pro-gateway/setup-worker-secrets.sh | 72 +
pro-gateway/src/index.js | 1003 +++++
pro-gateway/test/smoke-batch.mjs | 141 +
pro-gateway/test/smoke.mjs | 190 +
pro-gateway/wrangler.toml | 62 +
pro-gpu/.beamignore | 27 +
pro-gpu/README.md | 58 +
pro-gpu/app.py | 536 +++
pro-gpu/cleanup.py | 13 +
pro-gpu/estimate_foreground_ml_cupy.py | 232 ++
pro-gpu/kaggle_app.py | 277 ++
pro-gpu/setup-beam.sh | 31 +
pro-gpu/test_beam.py | 12 +
product-photo-background-remover/index.html | 2 +-
pt-br/id-photo-maker/index.html | 2 +-
pt-br/index.html | 1797 +++++----
pt-br/jpg-to-transparent-png/index.html | 2 +-
pt-br/old-photo-restoration/index.html | 2 +-
pt-br/png-to-jpg-white-background/index.html | 2 +-
pt-br/portrait-background-remover/index.html | 2 +-
.../index.html | 2 +-
pt-br/watermark-remover/index.html | 2 +-
quantize_birefnet.ipynb | 246 --
scripts/cloudflare-hf-gateway-worker.js | 176 +-
secret.txt | 1 +
sitemap.xml | 140 +-
src/input.css | 18 +
terms/index.html | 2 +-
ur/id-photo-maker/index.html | 2 +-
ur/index.html | 1797 +++++----
ur/jpg-to-transparent-png/index.html | 2 +-
ur/old-photo-restoration/index.html | 2 +-
ur/png-to-jpg-white-background/index.html | 2 +-
ur/portrait-background-remover/index.html | 2 +-
.../index.html | 2 +-
ur/watermark-remover/index.html | 2 +-
watermark-remover/index.html | 2 +-
watermark-remover/watermark.js | 8 +-
zh/id-photo-maker/index.html | 2 +-
zh/index.html | 1797 +++++----
zh/jpg-to-transparent-png/index.html | 2 +-
zh/old-photo-restoration/index.html | 2 +-
zh/png-to-jpg-white-background/index.html | 2 +-
zh/portrait-background-remover/index.html | 2 +-
.../index.html | 2 +-
zh/watermark-remover/index.html | 2 +-
118 files changed, 15100 insertions(+), 10880 deletions(-)
create mode 100644 .beamignore
create mode 100644 Dockerfile.openvino
create mode 100644 account/credits/index.html
create mode 100644 batch/batch.js
create mode 100644 batch/index.html
create mode 100644 gpu-scheduler/package-lock.json
create mode 100644 gpu-scheduler/package.json
create mode 100644 gpu-scheduler/src/index.ts
create mode 100644 gpu-scheduler/tsconfig.json
create mode 100644 gpu-scheduler/wrangler.toml
delete mode 100644 miaocut-seo-design-doc.md
delete mode 100644 old-photo-restoration-design.md
create mode 100644 pricing/index.html
create mode 100644 pro-account.js
create mode 100644 pro-gateway/README.md
create mode 100644 pro-gateway/db/schema-batch.sql
create mode 100644 pro-gateway/db/schema.sql
create mode 100755 pro-gateway/setup-worker-secrets.sh
create mode 100644 pro-gateway/src/index.js
create mode 100644 pro-gateway/test/smoke-batch.mjs
create mode 100644 pro-gateway/test/smoke.mjs
create mode 100644 pro-gateway/wrangler.toml
create mode 100644 pro-gpu/.beamignore
create mode 100644 pro-gpu/README.md
create mode 100644 pro-gpu/app.py
create mode 100644 pro-gpu/cleanup.py
create mode 100644 pro-gpu/estimate_foreground_ml_cupy.py
create mode 100644 pro-gpu/kaggle_app.py
create mode 100755 pro-gpu/setup-beam.sh
create mode 100644 pro-gpu/test_beam.py
delete mode 100644 quantize_birefnet.ipynb
create mode 100644 secret.txt
diff --git a/.beamignore b/.beamignore
new file mode 100644
index 0000000..8ae11be
--- /dev/null
+++ b/.beamignore
@@ -0,0 +1,30 @@
+# Generated by Beam SDK
+.beamignore
+.git
+.idea
+.python-version
+.vscode
+.venv
+venv
+__pycache__
+.DS_Store
+.config
+drive/MyDrive
+.coverage
+.pytest_cache
+.ipynb
+.ruff_cache
+.dockerignore
+.ipynb_checkpoints
+.env.local
+.envrc
+**/__pycache__/
+**/.pytest_cache/
+**/node_modules/
+**/.venv/
+*.pyc
+.next/
+.circleci
+.venv-py39-backup
+models
+
diff --git a/.claude/logs/tool-audit.jsonl b/.claude/logs/tool-audit.jsonl
index 3128b26..772bc90 100644
--- a/.claude/logs/tool-audit.jsonl
+++ b/.claude/logs/tool-audit.jsonl
@@ -709,3 +709,144 @@
{"ts": "2026-06-23T05:20:43Z", "session": "c9cd0dfe-ec58-45b6-9b0e-1dd462a0867e", "tool": "Edit", "input_summary": {"file": "/Users/wuchao/git/miaoCut/main.py"}, "ok": null}
{"ts": "2026-06-23T05:20:50Z", "session": "c9cd0dfe-ec58-45b6-9b0e-1dd462a0867e", "tool": "Edit", "input_summary": {"file": "/Users/wuchao/git/miaoCut/main.py"}, "ok": null}
{"ts": "2026-06-23T05:20:56Z", "session": "c9cd0dfe-ec58-45b6-9b0e-1dd462a0867e", "tool": "Bash", "input_summary": {"command": "{ [ -d .venv ] && source .venv/bin/activate 2>/dev/null; } ; python -c \"compile(open('main.py').read(),'main.py','exec'); print('compile OK')\" && grep -n \"_arena_run_options_for_now\\|_ARENA_SHRINK_RUN_OPTIONS\" main.py"}, "ok": null}
+{"ts": "2026-06-23T15:43:52Z", "session": "9a9f9a71-c626-4931-8a66-055a0b605f5a", "tool": "Read", "input_summary": {"file": "/Users/wuchao/git/miaoCut/docs/feature-pro.md"}, "ok": null}
+{"ts": "2026-06-23T15:43:53Z", "session": "9a9f9a71-c626-4931-8a66-055a0b605f5a", "tool": "Read", "input_summary": {"file": "/Users/wuchao/git/miaoCut/docs/feature-pro-design.md"}, "ok": null}
+{"ts": "2026-06-23T15:43:53Z", "session": "9a9f9a71-c626-4931-8a66-055a0b605f5a", "tool": "Read", "input_summary": {"file": "/Users/wuchao/git/miaoCut/docs/feature-pro-phase1.md"}, "ok": null}
+{"ts": "2026-06-23T15:44:05Z", "session": "9a9f9a71-c626-4931-8a66-055a0b605f5a", "tool": "Read", "input_summary": {"file": "/Users/wuchao/git/miaoCut/main.py"}, "ok": null}
+{"ts": "2026-06-23T15:44:06Z", "session": "9a9f9a71-c626-4931-8a66-055a0b605f5a", "tool": "Read", "input_summary": {"file": "/Users/wuchao/git/miaoCut/main.py"}, "ok": null}
+{"ts": "2026-06-23T16:09:28Z", "session": "9a9f9a71-c626-4931-8a66-055a0b605f5a", "tool": "AskUserQuestion", "input_summary": {"questions": [{"question": "这台 GPU 服务器你打算怎么托管?这决定网络/安全/成本模型。", "header": "GPU 宿主", "options": [{"label": "云按需租用", "description": "Runpod / Lambda / Vast.ai 等。弹性、单价低、无需自己运维硬件,用完即停省钱。CF Tunnel 接入最干净。适合先跑起来。"}, {"label": "自建硬件", "description": "家里/机房放 4090/3090 等。长期单位成本最低,一次性投入低,但要自己管电源/散热/网络/可用性,CF Tunnel 暴露。"}, {"label": "大厂云 GPU", "description": "AWS/GCP/阿里云 GPU 实例。稳定、合规、计费透明,但单价明显贵,适合规模上来后。"}], "multiSelect": false}, {"question": "GPU 服务接管哪些流量?这决定成本和 HF Spaces 的去留。", "header": "服务范围", "options": [{"label": "只接管付费路径", "description": "单图 HD + 批量走 GPU;免费匿名单图继续留 HF CPU 当 SEO 漏斗。成本可控,与双网关方案吻合。推荐。"}, {"label": "全部迁到 GPU", "description": "含免费匿名路径都上 GPU,质量统一拉满,HF 仅作故障转移。体验最好但 GPU 成本随免费流量线性增长。"}, {"label": "先只做批量", "description": "GPU 暂时只跑批量付费任务,单图(免费+登录)维持现状。改动面最小,先验证批量变现。"}], "multiSelect": false}, {"question": "批量任务怎么和 GPU 服务对接?这是一个真实的架构分叉。", "header": "批量模式", "options": [{"label": "GPU 端拉取 + 自管 R2", "description": "GPU 服务从队列拉任务、自己读写 R2,在 GPU 端做动态批处理。吞吐最高、最能打满显卡。推荐(批量的核心价值)。"}, {"label": "网关推送给 GPU", "description": "Pro 网关/Worker 把图推给 GPU 的 /batch 接口,GPU 现取现做。实现最简单,MVP 友好,但批处理效率略低。"}, {"label": "本阶段不做批量", "description": "先只把单图同步链路(秒级 HD)在 GPU 上打通,批量后置。"}], "multiSelect": false}], "answers": {"这台 GPU 服务器你打算怎么托管?这决定网络/安全/成本模型。": "云按需租用", "GPU 服务接管哪些流量?这决定成本和 HF Spaces 的去留。": "只接管付费路径", "批量任务怎么和 GPU 服务对接?这是一个真实的架构分叉。": "这个问题我们再深入讨论一下,我的想法是能否和现在的方式一样,直接通过网关上传到gpu服务器,让服务器处理解压和批量处理,它和上传到R2上再拉取下来批量处理对比为什么会效率低呢,主要在哪会效率低?"}}, "ok": null}
+{"ts": "2026-06-23T17:09:52Z", "session": "9a9f9a71-c626-4931-8a66-055a0b605f5a", "tool": "Write", "input_summary": {"file": "/private/tmp/gen_nb.py"}, "ok": null}
+{"ts": "2026-06-23T17:11:02Z", "session": "9a9f9a71-c626-4931-8a66-055a0b605f5a", "tool": "Bash", "input_summary": {"command": "python3 /private/tmp/gen_nb.py && python3 -c \"import json,sys; nb=json.load(open('benchmark/miaocut_gpu_benchmark.ipynb')); print('valid json, nbformat', nb['nbformat'], 'cells', len(nb['cells']))\""}, "ok": null}
+{"ts": "2026-06-23T17:11:21Z", "session": "9a9f9a71-c626-4931-8a66-055a0b605f5a", "tool": "Bash", "input_summary": {"command": "python3 - <<'PY'\nimport json\nnb = json.load(open('benchmark/miaocut_gpu_benchmark.ipynb'))\nimport ast\nok = True\nfor i, c in enumerate(nb['cells']):\n if c['cell_type'] != 'code':\n continue\n src = ''.join(c['source'])\n # 去掉 Colab 的 ! shell 行再做语法检查(这些不是合法 Python)\n lines = [l for l in src.splitlines() if not l.lstrip().startswith('!')]\n code = '\\n'.join(lines)\n try:\n ast.parse(code)\n except SyntaxError as e:\n ok = False\n print(f'cell {i} SyntaxError: "}, "ok": null}
+{"ts": "2026-06-23T17:11:50Z", "session": "9a9f9a71-c626-4931-8a66-055a0b605f5a", "tool": "Write", "input_summary": {"file": "/Users/wuchao/git/miaoCut/benchmark/README.md"}, "ok": null}
+{"ts": "2026-06-23T18:15:35Z", "session": "9a9f9a71-c626-4931-8a66-055a0b605f5a", "tool": "Edit", "input_summary": {"file": "/private/tmp/gen_nb.py"}, "ok": null}
+{"ts": "2026-06-23T18:15:42Z", "session": "9a9f9a71-c626-4931-8a66-055a0b605f5a", "tool": "Edit", "input_summary": {"file": "/private/tmp/gen_nb.py"}, "ok": null}
+{"ts": "2026-06-23T18:15:47Z", "session": "9a9f9a71-c626-4931-8a66-055a0b605f5a", "tool": "Edit", "input_summary": {"file": "/private/tmp/gen_nb.py"}, "ok": null}
+{"ts": "2026-06-23T18:15:54Z", "session": "9a9f9a71-c626-4931-8a66-055a0b605f5a", "tool": "Edit", "input_summary": {"file": "/private/tmp/gen_nb.py"}, "ok": null}
+{"ts": "2026-06-23T18:16:05Z", "session": "9a9f9a71-c626-4931-8a66-055a0b605f5a", "tool": "Edit", "input_summary": {"file": "/private/tmp/gen_nb.py"}, "ok": null}
+{"ts": "2026-06-23T18:16:12Z", "session": "9a9f9a71-c626-4931-8a66-055a0b605f5a", "tool": "Bash", "input_summary": {"command": "python3 /private/tmp/gen_nb.py && python3 - <<'PY'\nimport json, ast\nnb = json.load(open('benchmark/miaocut_gpu_benchmark.ipynb'))\nok = True\nfor i, c in enumerate(nb['cells']):\n if c['cell_type'] != 'code': continue\n code = '\\n'.join(l for l in ''.join(c['source']).splitlines() if not l.lstrip().startswith('!'))\n try: ast.parse(code)\n except SyntaxError as e: ok = False; print(f'cell {i} SyntaxError: {e}')\nprint('valid json + ALL CODE CELLS OK' if ok else 'SYNTAX ERRORS')\nPY"}, "ok": null}
+{"ts": "2026-06-23T18:44:18Z", "session": "9a9f9a71-c626-4931-8a66-055a0b605f5a", "tool": "Edit", "input_summary": {"file": "/private/tmp/gen_nb.py"}, "ok": null}
+{"ts": "2026-06-23T18:44:25Z", "session": "9a9f9a71-c626-4931-8a66-055a0b605f5a", "tool": "Bash", "input_summary": {"command": "python3 /private/tmp/gen_nb.py && python3 - <<'PY'\nimport json, ast\nnb = json.load(open('benchmark/miaocut_gpu_benchmark.ipynb'))\nok = True\nfor i, c in enumerate(nb['cells']):\n if c['cell_type'] != 'code': continue\n code = '\\n'.join(l for l in ''.join(c['source']).splitlines() if not l.lstrip().startswith('!'))\n try: ast.parse(code)\n except SyntaxError as e: ok = False; print(f'cell {i} SyntaxError: {e}')\nprint('valid + ALL CODE CELLS OK' if ok else 'SYNTAX ERRORS')\nPY"}, "ok": null}
+{"ts": "2026-06-23T19:03:16Z", "session": "9a9f9a71-c626-4931-8a66-055a0b605f5a", "tool": "Edit", "input_summary": {"file": "/private/tmp/gen_nb.py"}, "ok": null}
+{"ts": "2026-06-23T19:03:25Z", "session": "9a9f9a71-c626-4931-8a66-055a0b605f5a", "tool": "Bash", "input_summary": {"command": "python3 /private/tmp/gen_nb.py && python3 -c \"import json,ast; nb=json.load(open('benchmark/miaocut_gpu_benchmark.ipynb')); [ast.parse('\\n'.join(l for l in ''.join(c['source']).splitlines() if not l.lstrip().startswith('!'))) for c in nb['cells'] if c['cell_type']=='code']; print('valid + code OK')\""}, "ok": null}
+{"ts": "2026-06-23T19:25:32Z", "session": "9a9f9a71-c626-4931-8a66-055a0b605f5a", "tool": "Edit", "input_summary": {"file": "/private/tmp/gen_nb.py"}, "ok": null}
+{"ts": "2026-06-23T19:25:38Z", "session": "9a9f9a71-c626-4931-8a66-055a0b605f5a", "tool": "Read", "input_summary": {"file": "/Users/wuchao/.claude/projects/-Users-wuchao-git-miaoCut/memory/MEMORY.md"}, "ok": null}
+{"ts": "2026-06-23T19:25:50Z", "session": "9a9f9a71-c626-4931-8a66-055a0b605f5a", "tool": "Write", "input_summary": {"file": "/Users/wuchao/.claude/projects/-Users-wuchao-git-miaoCut/memory/pro-gpu-deploy-arch.md"}, "ok": null}
+{"ts": "2026-06-23T19:26:00Z", "session": "9a9f9a71-c626-4931-8a66-055a0b605f5a", "tool": "Write", "input_summary": {"file": "/Users/wuchao/.claude/projects/-Users-wuchao-git-miaoCut/memory/pro-gpu-model-choice.md"}, "ok": null}
+{"ts": "2026-06-23T19:26:08Z", "session": "9a9f9a71-c626-4931-8a66-055a0b605f5a", "tool": "Edit", "input_summary": {"file": "/Users/wuchao/.claude/projects/-Users-wuchao-git-miaoCut/memory/MEMORY.md"}, "ok": null}
+{"ts": "2026-06-23T19:26:18Z", "session": "9a9f9a71-c626-4931-8a66-055a0b605f5a", "tool": "Bash", "input_summary": {"command": "python3 /private/tmp/gen_nb.py && python3 -c \"import json,ast; nb=json.load(open('benchmark/miaocut_gpu_benchmark.ipynb')); [ast.parse('\\n'.join(l for l in ''.join(c['source']).splitlines() if not l.lstrip().startswith('!'))) for c in nb['cells'] if c['cell_type']=='code']; print('notebook 已更新默认配置为 matting,校验通过')\""}, "ok": null}
+{"ts": "2026-06-23T19:53:49Z", "session": "9a9f9a71-c626-4931-8a66-055a0b605f5a", "tool": "Edit", "input_summary": {"file": "/Users/wuchao/.claude/projects/-Users-wuchao-git-miaoCut/memory/pro-gpu-model-choice.md"}, "ok": null}
+{"ts": "2026-06-23T19:54:56Z", "session": "9a9f9a71-c626-4931-8a66-055a0b605f5a", "tool": "Write", "input_summary": {"file": "/Users/wuchao/git/miaoCut/docs/feature-pro-gpu-service.md"}, "ok": null}
+{"ts": "2026-06-23T19:58:23Z", "session": "9a9f9a71-c626-4931-8a66-055a0b605f5a", "tool": "AskUserQuestion", "input_summary": {"questions": [{"question": "serverless GPU 用哪个平台?这决定部署脚本、冷启动方式、计费粒度。", "header": "serverless 平台", "options": [{"label": "Modal", "description": "Python 原生、内存快照让冷启动很快、按秒计费、部署体验最好。适合 FastAPI + 自定义后处理这种场景,我最推荐。"}, {"label": "Runpod Serverless", "description": "GPU 型号选择多、单价低、社区成熟。冷启动比 Modal 略逊,但更省钱。"}, {"label": "Replicate", "description": "把模型打包成 predictor 最省心,但灵活性低(自定义 FastAPI/后处理受限)、单价偏高。"}], "multiSelect": false}, {"question": "缩容策略怎么定?这是省钱和首请求延迟的权衡。", "header": "缩容策略", "options": [{"label": "纯 scale-to-zero", "description": "闲置缩到 0,零常驻成本;首请求要等冷启动(数秒)。早期付费量稀疏,先这样最省钱,体验不行再调。推荐起步。"}, {"label": "matting 保温 + HR 懒加载", "description": "fast 档(小模型)保 1 个常温实例,低延迟;fur 档(HR)容忍冷启动。折中方案。"}, {"label": "保 1 个常温实例", "description": "首请求都低延迟,但有常驻成本(约 $300+/月,取决于卡)。付费量起来后再考虑。"}], "multiSelect": false}, {"question": "GPU 服务第一版接口范围做到哪?", "header": "接口范围", "options": [{"label": "仅透明 PNG", "description": "fast/fur 两档 + 去彩边,输出透明 PNG。最快上线,背景合成/多格式留 Phase 2。推荐先跑通闭环。"}, {"label": "加纯色背景合成", "description": "多一个 bg 参数支持白底/纯色底(电商常用),id-photo 已有服务端合成可复用。接口面稍大。"}, {"label": "背景合成 + 多格式", "description": "再加 webp/jpg 白底输出。接口最全但上线最慢。"}], "multiSelect": false}], "answers": {"serverless GPU 用哪个平台?这决定部署脚本、冷启动方式、计费粒度。": "Beam", "缩容策略怎么定?这是省钱和首请求延迟的权衡。": "纯 scale-to-zero", "GPU 服务第一版接口范围做到哪?": "仅透明 PNG"}}, "ok": null}
+{"ts": "2026-06-23T20:01:52Z", "session": "9a9f9a71-c626-4931-8a66-055a0b605f5a", "tool": "Write", "input_summary": {"file": "/Users/wuchao/git/miaoCut/docs/feature-pro-gpu-service.md"}, "ok": null}
+{"ts": "2026-06-23T20:02:10Z", "session": "9a9f9a71-c626-4931-8a66-055a0b605f5a", "tool": "Write", "input_summary": {"file": "/Users/wuchao/.claude/projects/-Users-wuchao-git-miaoCut/memory/pro-gpu-deploy-arch.md"}, "ok": null}
+{"ts": "2026-06-23T20:11:58Z", "session": "9a9f9a71-c626-4931-8a66-055a0b605f5a", "tool": "Edit", "input_summary": {"file": "/Users/wuchao/git/miaoCut/docs/feature-pro-gpu-service.md"}, "ok": null}
+{"ts": "2026-06-23T20:12:11Z", "session": "9a9f9a71-c626-4931-8a66-055a0b605f5a", "tool": "Edit", "input_summary": {"file": "/Users/wuchao/git/miaoCut/docs/feature-pro-gpu-service.md"}, "ok": null}
+{"ts": "2026-06-23T20:12:53Z", "session": "9a9f9a71-c626-4931-8a66-055a0b605f5a", "tool": "Write", "input_summary": {"file": "/Users/wuchao/git/miaoCut/pro-gpu/app.py"}, "ok": null}
+{"ts": "2026-06-23T20:13:19Z", "session": "9a9f9a71-c626-4931-8a66-055a0b605f5a", "tool": "Write", "input_summary": {"file": "/Users/wuchao/git/miaoCut/pro-gpu/README.md"}, "ok": null}
+{"ts": "2026-06-23T20:13:28Z", "session": "9a9f9a71-c626-4931-8a66-055a0b605f5a", "tool": "Edit", "input_summary": {"file": "/Users/wuchao/.claude/projects/-Users-wuchao-git-miaoCut/memory/pro-gpu-deploy-arch.md"}, "ok": null}
+{"ts": "2026-06-23T20:13:38Z", "session": "9a9f9a71-c626-4931-8a66-055a0b605f5a", "tool": "Bash", "input_summary": {"command": "python3 -m py_compile pro-gpu/app.py && echo \"py_compile OK: pro-gpu/app.py 语法通过\""}, "ok": null}
+{"ts": "2026-06-24T02:47:26Z", "session": "9a9f9a71-c626-4931-8a66-055a0b605f5a", "tool": "Edit", "input_summary": {"file": "/Users/wuchao/git/miaoCut/docs/feature-pro-gpu-service.md"}, "ok": null}
+{"ts": "2026-06-24T02:47:45Z", "session": "9a9f9a71-c626-4931-8a66-055a0b605f5a", "tool": "Edit", "input_summary": {"file": "/Users/wuchao/.claude/projects/-Users-wuchao-git-miaoCut/memory/pro-gpu-deploy-arch.md"}, "ok": null}
+{"ts": "2026-06-24T02:52:28Z", "session": "9a9f9a71-c626-4931-8a66-055a0b605f5a", "tool": "Edit", "input_summary": {"file": "/Users/wuchao/git/miaoCut/docs/feature-pro-gpu-service.md"}, "ok": null}
+{"ts": "2026-06-24T02:52:44Z", "session": "9a9f9a71-c626-4931-8a66-055a0b605f5a", "tool": "Edit", "input_summary": {"file": "/Users/wuchao/.claude/projects/-Users-wuchao-git-miaoCut/memory/pro-gpu-deploy-arch.md"}, "ok": null}
+{"ts": "2026-06-24T02:56:39Z", "session": "9a9f9a71-c626-4931-8a66-055a0b605f5a", "tool": "Read", "input_summary": {"file": "/Users/wuchao/git/miaoCut/pro-gateway/src/index.js"}, "ok": null}
+{"ts": "2026-06-24T02:56:40Z", "session": "9a9f9a71-c626-4931-8a66-055a0b605f5a", "tool": "Read", "input_summary": {"file": "/Users/wuchao/git/miaoCut/pro-gateway/wrangler.toml"}, "ok": null}
+{"ts": "2026-06-24T02:59:18Z", "session": "9a9f9a71-c626-4931-8a66-055a0b605f5a", "tool": "Read", "input_summary": {"file": "/Users/wuchao/git/miaoCut/pro-gateway/README.md"}, "ok": null}
+{"ts": "2026-06-24T03:00:56Z", "session": "9a9f9a71-c626-4931-8a66-055a0b605f5a", "tool": "Write", "input_summary": {"file": "/Users/wuchao/git/miaoCut/pro-gateway/src/index.js"}, "ok": null}
+{"ts": "2026-06-24T03:01:17Z", "session": "9a9f9a71-c626-4931-8a66-055a0b605f5a", "tool": "Write", "input_summary": {"file": "/Users/wuchao/git/miaoCut/pro-gateway/wrangler.toml"}, "ok": null}
+{"ts": "2026-06-24T03:01:48Z", "session": "9a9f9a71-c626-4931-8a66-055a0b605f5a", "tool": "Write", "input_summary": {"file": "/Users/wuchao/git/miaoCut/pro-gateway/README.md"}, "ok": null}
+{"ts": "2026-06-24T03:02:32Z", "session": "9a9f9a71-c626-4931-8a66-055a0b605f5a", "tool": "Bash", "input_summary": {"command": "node --check pro-gateway/src/index.js && echo \"node --check OK: pro-gateway/src/index.js\""}, "ok": null}
+{"ts": "2026-06-24T03:14:23Z", "session": "9a9f9a71-c626-4931-8a66-055a0b605f5a", "tool": "Read", "input_summary": {"file": "/Users/wuchao/git/miaoCut/pro-account.js"}, "ok": null}
+{"ts": "2026-06-24T03:14:24Z", "session": "9a9f9a71-c626-4931-8a66-055a0b605f5a", "tool": "Bash", "input_summary": {"command": "node --version; echo \"---\"; grep -n \"API_BASE\\|api2.miaocut\\|XMLHttpRequest\\|remove-background\" /Users/wuchao/git/miaoCut/app.js | head -40"}, "ok": null}
+{"ts": "2026-06-24T03:16:38Z", "session": "9a9f9a71-c626-4931-8a66-055a0b605f5a", "tool": "Edit", "input_summary": {"file": "/Users/wuchao/git/miaoCut/pro-account.js"}, "ok": null}
+{"ts": "2026-06-24T03:17:19Z", "session": "9a9f9a71-c626-4931-8a66-055a0b605f5a", "tool": "Write", "input_summary": {"file": "/Users/wuchao/git/miaoCut/pro-gateway/test/smoke.mjs"}, "ok": null}
+{"ts": "2026-06-24T03:17:32Z", "session": "9a9f9a71-c626-4931-8a66-055a0b605f5a", "tool": "Bash", "input_summary": {"command": "node pro-gateway/test/smoke.mjs"}, "ok": null}
+{"ts": "2026-06-24T03:19:35Z", "session": "9a9f9a71-c626-4931-8a66-055a0b605f5a", "tool": "Read", "input_summary": {"file": "/Users/wuchao/git/miaoCut/app.js"}, "ok": null}
+{"ts": "2026-06-24T03:19:36Z", "session": "9a9f9a71-c626-4931-8a66-055a0b605f5a", "tool": "Read", "input_summary": {"file": "/Users/wuchao/git/miaoCut/app.js"}, "ok": null}
+{"ts": "2026-06-24T03:19:48Z", "session": "9a9f9a71-c626-4931-8a66-055a0b605f5a", "tool": "Bash", "input_summary": {"command": "grep -n \"uploadWithProgress\\|currentProfile\\s*=\\|err\\.status\\|=== 429\\|== 429\\|function handleFile\\|processFile\\|fileInput\\|addEventListener('change'\\|openWaitlistModal('rateDay'\\|MiaoCutPro\\|pro-account\" app.js | head -50"}, "ok": null}
+{"ts": "2026-06-24T03:19:58Z", "session": "9a9f9a71-c626-4931-8a66-055a0b605f5a", "tool": "Read", "input_summary": {"file": "/Users/wuchao/git/miaoCut/app.js"}, "ok": null}
+{"ts": "2026-06-24T03:19:59Z", "session": "9a9f9a71-c626-4931-8a66-055a0b605f5a", "tool": "Read", "input_summary": {"file": "/Users/wuchao/git/miaoCut/app.js"}, "ok": null}
+{"ts": "2026-06-24T03:22:35Z", "session": "9a9f9a71-c626-4931-8a66-055a0b605f5a", "tool": "Edit", "input_summary": {"file": "/Users/wuchao/git/miaoCut/app.js"}, "ok": null}
+{"ts": "2026-06-24T03:22:45Z", "session": "9a9f9a71-c626-4931-8a66-055a0b605f5a", "tool": "Edit", "input_summary": {"file": "/Users/wuchao/git/miaoCut/app.js"}, "ok": null}
+{"ts": "2026-06-24T03:22:54Z", "session": "9a9f9a71-c626-4931-8a66-055a0b605f5a", "tool": "Edit", "input_summary": {"file": "/Users/wuchao/git/miaoCut/app.js"}, "ok": null}
+{"ts": "2026-06-24T03:23:02Z", "session": "9a9f9a71-c626-4931-8a66-055a0b605f5a", "tool": "Bash", "input_summary": {"command": "node --check app.js && echo \"app.js 语法 OK\"; echo \"---\"; grep -n \"app\\.js\\|pro-account\\|MIAOCUT_PRO_CONFIG\\|\\|
&1 | tail -20; echo \"=== 改动文件 ===\"; git status --short | head -30"}, "ok": null}
+{"ts": "2026-06-24T03:24:20Z", "session": "9a9f9a71-c626-4931-8a66-055a0b605f5a", "tool": "Bash", "input_summary": {"command": "echo \"=== zh/index.html 是否含 pro-account ===\"; grep -c \"pro-account.js\\|MIAOCUT_PRO_CONFIG\" zh/index.html; echo \"=== sitemap 是否实质变化 ===\"; git diff --stat sitemap.xml; echo \"=== 本地化首页改动行数(应只增脚本) ===\"; git diff --stat zh/index.html bn/index.html"}, "ok": null}
+{"ts": "2026-06-24T05:27:18Z", "session": "9a9f9a71-c626-4931-8a66-055a0b605f5a", "tool": "ToolSearch", "input_summary": {"query": "select:WebSearch,WebFetch", "max_results": 5}, "ok": null}
+{"ts": "2026-06-24T05:27:43Z", "session": "9a9f9a71-c626-4931-8a66-055a0b605f5a", "tool": "WebSearch", "input_summary": {"query": "visitor to paid conversion rate website freemium tool percentage benchmark"}, "ok": null}
+{"ts": "2026-06-24T05:27:44Z", "session": "9a9f9a71-c626-4931-8a66-055a0b605f5a", "tool": "WebSearch", "input_summary": {"query": "freemium free to paid conversion rate benchmark 2025 SaaS consumer app median"}, "ok": null}
+{"ts": "2026-06-25T04:14:03Z", "session": "9a9f9a71-c626-4931-8a66-055a0b605f5a", "tool": "Write", "input_summary": {"file": "/Users/wuchao/git/miaoCut/pro-gpu/app.py"}, "ok": null}
+{"ts": "2026-06-25T04:14:14Z", "session": "9a9f9a71-c626-4931-8a66-055a0b605f5a", "tool": "Bash", "input_summary": {"command": "python3 -m py_compile pro-gpu/app.py && echo \"py_compile OK: pro-gpu/app.py(含 @task_queue 批量函数)语法通过\""}, "ok": null}
+{"ts": "2026-06-25T04:14:36Z", "session": "9a9f9a71-c626-4931-8a66-055a0b605f5a", "tool": "Read", "input_summary": {"file": "/Users/wuchao/.claude/projects/-Users-wuchao-git-miaoCut/memory/pro-gpu-deploy-arch.md"}, "ok": null}
+{"ts": "2026-06-25T04:15:00Z", "session": "9a9f9a71-c626-4931-8a66-055a0b605f5a", "tool": "Edit", "input_summary": {"file": "/Users/wuchao/.claude/projects/-Users-wuchao-git-miaoCut/memory/pro-gpu-deploy-arch.md"}, "ok": null}
+{"ts": "2026-06-25T04:15:13Z", "session": "9a9f9a71-c626-4931-8a66-055a0b605f5a", "tool": "Edit", "input_summary": {"file": "/Users/wuchao/.claude/projects/-Users-wuchao-git-miaoCut/memory/pro-gpu-deploy-arch.md"}, "ok": null}
+{"ts": "2026-06-25T04:15:22Z", "session": "9a9f9a71-c626-4931-8a66-055a0b605f5a", "tool": "Edit", "input_summary": {"file": "/Users/wuchao/.claude/projects/-Users-wuchao-git-miaoCut/memory/pro-gpu-deploy-arch.md"}, "ok": null}
+{"ts": "2026-06-25T04:15:33Z", "session": "9a9f9a71-c626-4931-8a66-055a0b605f5a", "tool": "Read", "input_summary": {"file": "/Users/wuchao/git/miaoCut/docs/feature-pro-gpu-service.md"}, "ok": null}
+{"ts": "2026-06-25T04:16:05Z", "session": "9a9f9a71-c626-4931-8a66-055a0b605f5a", "tool": "Edit", "input_summary": {"file": "/Users/wuchao/git/miaoCut/docs/feature-pro-gpu-service.md"}, "ok": null}
+{"ts": "2026-06-25T04:16:14Z", "session": "9a9f9a71-c626-4931-8a66-055a0b605f5a", "tool": "Edit", "input_summary": {"file": "/Users/wuchao/git/miaoCut/docs/feature-pro-gpu-service.md"}, "ok": null}
+{"ts": "2026-06-25T04:16:34Z", "session": "9a9f9a71-c626-4931-8a66-055a0b605f5a", "tool": "Edit", "input_summary": {"file": "/Users/wuchao/git/miaoCut/docs/feature-pro-gpu-service.md"}, "ok": null}
+{"ts": "2026-06-25T04:16:47Z", "session": "9a9f9a71-c626-4931-8a66-055a0b605f5a", "tool": "Edit", "input_summary": {"file": "/Users/wuchao/git/miaoCut/docs/feature-pro-gpu-service.md"}, "ok": null}
+{"ts": "2026-06-25T04:33:51Z", "session": "a16e301f-73ed-4c83-ab43-9247848abc4f", "tool": "Bash", "input_summary": {"command": "cd /Users/wuchao/git/miaoCut\necho \"===== 找 wrangler / worker 配置文件 =====\"\nfind . -maxdepth 3 \\( -iname \"wrangler.toml\" -o -iname \"wrangler.json\" -o -iname \"wrangler.jsonc\" \\) 2>/dev/null | grep -v node_modules\necho \"\"\necho \"===== 仓库里任何提到 RATE_LIMITER / ratelimit binding 的地方 =====\"\ngrep -rniE \"RATE_LIMITER|ratelimit|\\[\\[ratelimits\\]\\]|kv_namespace|namespace_id\" . --include=\"*.toml\" --include=\"*.json\" --include=\"*.jsonc\" 2>/dev/null | grep -v node_modules | head -20\necho \"\"\necho \"(若上面都为空,说明 binding"}, "ok": null}
+{"ts": "2026-06-25T04:35:07Z", "session": "a16e301f-73ed-4c83-ab43-9247848abc4f", "tool": "Edit", "input_summary": {"file": "/Users/wuchao/git/miaoCut/scripts/cloudflare-hf-gateway-worker.js"}, "ok": null}
+{"ts": "2026-06-25T04:35:17Z", "session": "a16e301f-73ed-4c83-ab43-9247848abc4f", "tool": "Edit", "input_summary": {"file": "/Users/wuchao/git/miaoCut/scripts/cloudflare-hf-gateway-worker.js"}, "ok": null}
+{"ts": "2026-06-25T04:35:30Z", "session": "a16e301f-73ed-4c83-ab43-9247848abc4f", "tool": "Edit", "input_summary": {"file": "/Users/wuchao/git/miaoCut/scripts/cloudflare-hf-gateway-worker.js"}, "ok": null}
+{"ts": "2026-06-25T04:35:40Z", "session": "a16e301f-73ed-4c83-ab43-9247848abc4f", "tool": "Bash", "input_summary": {"command": "cd /Users/wuchao/git/miaoCut\nnode --check scripts/cloudflare-hf-gateway-worker.js && echo \"Worker JS 语法 OK\"\necho \"\"\necho \"===== pro-gateway/wrangler.toml 里有没有 ratelimit/kv binding(确认它是另一个 worker) =====\"\ngrep -niE \"name|ratelimit|kv_namespace|binding|main\" pro-gateway/wrangler.toml 2>/dev/null | head"}, "ok": null}
+{"ts": "2026-06-25T04:50:13Z", "session": "a16e301f-73ed-4c83-ab43-9247848abc4f", "tool": "Edit", "input_summary": {"file": "/Users/wuchao/git/miaoCut/scripts/cloudflare-hf-gateway-worker.js"}, "ok": null}
+{"ts": "2026-06-25T04:50:21Z", "session": "a16e301f-73ed-4c83-ab43-9247848abc4f", "tool": "Edit", "input_summary": {"file": "/Users/wuchao/git/miaoCut/scripts/cloudflare-hf-gateway-worker.js"}, "ok": null}
+{"ts": "2026-06-25T04:50:51Z", "session": "a16e301f-73ed-4c83-ab43-9247848abc4f", "tool": "Edit", "input_summary": {"file": "/Users/wuchao/git/miaoCut/scripts/cloudflare-hf-gateway-worker.js"}, "ok": null}
+{"ts": "2026-06-25T04:50:58Z", "session": "a16e301f-73ed-4c83-ab43-9247848abc4f", "tool": "Bash", "input_summary": {"command": "cd /Users/wuchao/git/miaoCut\nnode --check scripts/cloudflare-hf-gateway-worker.js && echo \"Worker JS 语法 OK\"\necho \"\"\necho \"===== 边缘限流那段现在长这样(确认回到官方等价写法) =====\"\nsed -n '14,45p' scripts/cloudflare-hf-gateway-worker.js"}, "ok": null}
+{"ts": "2026-06-25T05:04:10Z", "session": "a16e301f-73ed-4c83-ab43-9247848abc4f", "tool": "Read", "input_summary": {"file": "/Users/wuchao/git/miaoCut/scripts/cloudflare-hf-gateway-worker.js"}, "ok": null}
+{"ts": "2026-06-25T05:07:41Z", "session": "a16e301f-73ed-4c83-ab43-9247848abc4f", "tool": "Bash", "input_summary": {"command": "cd /Users/wuchao/git/miaoCut\necho \"========== pro-gateway/ 文件树 ==========\"\nfind pro-gateway -type f -not -path '*/node_modules/*' -not -path '*/.git/*' | sort\necho \"\"\necho \"========== pro-gpu/ 文件树 ==========\"\nfind pro-gpu -type f -not -path '*/node_modules/*' -not -path '*/.git/*' | sort\necho \"\"\necho \"========== docs/feature-pro* 文档 ==========\"\nls -la docs/feature-pro*.md 2>/dev/null\necho \"\"\necho \"========== 各文件行数概览 ==========\"\nwc -l pro-gateway/**/*.* pro-gpu/**/*.* 2>/dev/null | tail -30"}, "ok": null}
+{"ts": "2026-06-25T05:08:07Z", "session": "a16e301f-73ed-4c83-ab43-9247848abc4f", "tool": "Read", "input_summary": {"file": "/Users/wuchao/git/miaoCut/docs/feature-pro.md"}, "ok": null}
+{"ts": "2026-06-25T05:08:20Z", "session": "a16e301f-73ed-4c83-ab43-9247848abc4f", "tool": "Read", "input_summary": {"file": "/Users/wuchao/git/miaoCut/pro-gateway/README.md"}, "ok": null}
+{"ts": "2026-06-25T05:08:33Z", "session": "a16e301f-73ed-4c83-ab43-9247848abc4f", "tool": "Read", "input_summary": {"file": "/Users/wuchao/git/miaoCut/pro-gpu/README.md"}, "ok": null}
+{"ts": "2026-06-25T14:51:03Z", "session": "64950bfd-f3fc-4676-81c2-5af4e58bb133", "tool": "Bash", "input_summary": {"command": "grep -n \"429\\|limiter\\|limit(\\|semaphore\\|Semaphore\\|RateLimit\\|TooManyRequests\\|503\\|acquire\\|Retry-After\\|retry_after\" main.py"}, "ok": null}
+{"ts": "2026-06-25T14:51:10Z", "session": "64950bfd-f3fc-4676-81c2-5af4e58bb133", "tool": "Read", "input_summary": {"file": "/Users/wuchao/git/miaoCut/main.py"}, "ok": null}
+{"ts": "2026-06-25T14:51:14Z", "session": "64950bfd-f3fc-4676-81c2-5af4e58bb133", "tool": "Read", "input_summary": {"file": "/Users/wuchao/git/miaoCut/main.py"}, "ok": null}
+{"ts": "2026-06-25T14:51:18Z", "session": "64950bfd-f3fc-4676-81c2-5af4e58bb133", "tool": "Read", "input_summary": {"file": "/Users/wuchao/git/miaoCut/main.py"}, "ok": null}
+{"ts": "2026-06-25T14:51:22Z", "session": "64950bfd-f3fc-4676-81c2-5af4e58bb133", "tool": "Read", "input_summary": {"file": "/Users/wuchao/git/miaoCut/main.py"}, "ok": null}
+{"ts": "2026-06-25T14:51:26Z", "session": "64950bfd-f3fc-4676-81c2-5af4e58bb133", "tool": "Read", "input_summary": {"file": "/Users/wuchao/git/miaoCut/main.py"}, "ok": null}
+{"ts": "2026-06-25T14:56:41Z", "session": "64950bfd-f3fc-4676-81c2-5af4e58bb133", "tool": "Bash", "input_summary": {"command": "grep -n \"expose_headers\\|CORSMiddleware\\|allow_headers\\|X-RateLimit-Scope\\|add_middleware\" main.py"}, "ok": null}
+{"ts": "2026-06-25T14:56:45Z", "session": "64950bfd-f3fc-4676-81c2-5af4e58bb133", "tool": "Read", "input_summary": {"file": "/Users/wuchao/git/miaoCut/main.py"}, "ok": null}
+{"ts": "2026-06-25T14:57:49Z", "session": "64950bfd-f3fc-4676-81c2-5af4e58bb133", "tool": "Read", "input_summary": {"file": "/Users/wuchao/git/miaoCut/main.py"}, "ok": null}
+{"ts": "2026-06-25T14:57:53Z", "session": "64950bfd-f3fc-4676-81c2-5af4e58bb133", "tool": "Bash", "input_summary": {"command": "grep -n \"id-photo/create\\|id-photo/human-matting\\|async def create_id_photo\\|async def human_matting\\|file.read\\|await image.read\\|await file.read\\|run_id_photo\\|run_rembg(\" main.py | head -50"}, "ok": null}
+{"ts": "2026-06-25T14:58:02Z", "session": "64950bfd-f3fc-4676-81c2-5af4e58bb133", "tool": "Read", "input_summary": {"file": "/Users/wuchao/git/miaoCut/main.py"}, "ok": null}
+{"ts": "2026-06-25T14:58:14Z", "session": "64950bfd-f3fc-4676-81c2-5af4e58bb133", "tool": "Read", "input_summary": {"file": "/Users/wuchao/git/miaoCut/main.py"}, "ok": null}
+{"ts": "2026-06-25T14:58:27Z", "session": "64950bfd-f3fc-4676-81c2-5af4e58bb133", "tool": "Read", "input_summary": {"file": "/Users/wuchao/git/miaoCut/main.py"}, "ok": null}
+{"ts": "2026-06-25T14:58:39Z", "session": "64950bfd-f3fc-4676-81c2-5af4e58bb133", "tool": "Read", "input_summary": {"file": "/Users/wuchao/git/miaoCut/main.py"}, "ok": null}
+{"ts": "2026-06-25T14:58:43Z", "session": "64950bfd-f3fc-4676-81c2-5af4e58bb133", "tool": "Read", "input_summary": {"file": "/Users/wuchao/git/miaoCut/main.py"}, "ok": null}
+{"ts": "2026-06-25T14:59:13Z", "session": "64950bfd-f3fc-4676-81c2-5af4e58bb133", "tool": "Edit", "input_summary": {"file": "/Users/wuchao/git/miaoCut/main.py"}, "ok": null}
+{"ts": "2026-06-25T14:59:22Z", "session": "64950bfd-f3fc-4676-81c2-5af4e58bb133", "tool": "Edit", "input_summary": {"file": "/Users/wuchao/git/miaoCut/main.py"}, "ok": null}
+{"ts": "2026-06-25T14:59:26Z", "session": "64950bfd-f3fc-4676-81c2-5af4e58bb133", "tool": "Edit", "input_summary": {"file": "/Users/wuchao/git/miaoCut/main.py"}, "ok": null}
+{"ts": "2026-06-25T14:59:31Z", "session": "64950bfd-f3fc-4676-81c2-5af4e58bb133", "tool": "Edit", "input_summary": {"file": "/Users/wuchao/git/miaoCut/main.py"}, "ok": null}
+{"ts": "2026-06-25T14:59:41Z", "session": "64950bfd-f3fc-4676-81c2-5af4e58bb133", "tool": "Edit", "input_summary": {"file": "/Users/wuchao/git/miaoCut/main.py"}, "ok": null}
+{"ts": "2026-06-25T14:59:49Z", "session": "64950bfd-f3fc-4676-81c2-5af4e58bb133", "tool": "Edit", "input_summary": {"file": "/Users/wuchao/git/miaoCut/main.py"}, "ok": null}
+{"ts": "2026-06-25T15:00:11Z", "session": "64950bfd-f3fc-4676-81c2-5af4e58bb133", "tool": "Edit", "input_summary": {"file": "/Users/wuchao/git/miaoCut/main.py"}, "ok": null}
+{"ts": "2026-06-25T15:00:20Z", "session": "64950bfd-f3fc-4676-81c2-5af4e58bb133", "tool": "Edit", "input_summary": {"file": "/Users/wuchao/git/miaoCut/main.py"}, "ok": null}
+{"ts": "2026-06-25T15:00:29Z", "session": "64950bfd-f3fc-4676-81c2-5af4e58bb133", "tool": "Edit", "input_summary": {"file": "/Users/wuchao/git/miaoCut/main.py"}, "ok": null}
+{"ts": "2026-06-25T15:00:41Z", "session": "64950bfd-f3fc-4676-81c2-5af4e58bb133", "tool": "Edit", "input_summary": {"file": "/Users/wuchao/git/miaoCut/main.py"}, "ok": null}
+{"ts": "2026-06-25T15:00:48Z", "session": "64950bfd-f3fc-4676-81c2-5af4e58bb133", "tool": "Edit", "input_summary": {"file": "/Users/wuchao/git/miaoCut/main.py"}, "ok": null}
+{"ts": "2026-06-25T15:01:06Z", "session": "64950bfd-f3fc-4676-81c2-5af4e58bb133", "tool": "Bash", "input_summary": {"command": "python -m py_compile main.py && echo \"OK: py_compile passed\""}, "ok": null}
+{"ts": "2026-06-25T15:01:09Z", "session": "64950bfd-f3fc-4676-81c2-5af4e58bb133", "tool": "Bash", "input_summary": {"command": "grep -n \"reject_if_saturated\\|_saturated_429\\|X-Reason\\|expose_headers\" main.py"}, "ok": null}
+{"ts": "2026-06-25T15:01:24Z", "session": "64950bfd-f3fc-4676-81c2-5af4e58bb133", "tool": "Bash", "input_summary": {"command": "python - <<'PY'\nimport asyncio\n\nasync def main():\n sem = asyncio.Semaphore(1)\n print(\"初期 locked():\", sem.locked()) # False(空きあり)\n await sem.acquire()\n print(\"acquire 後 locked():\", sem.locked()) # True(満載)→ 予検でここ即429\n sem.release()\n print(\"release 後 locked():\", sem.locked()) # False\n\n # MAX_CONCURRENCY=2 のとき、1枠埋まっても満載ではない\n sem2 = asyncio.Semaphore(2)\n await sem2.acquire()\n print(\"Semaphore(2) 1枠取得後 locked():\", sem2.locked()) # False(まだ空きあり=予検は通す)\n "}, "ok": null}
+{"ts": "2026-06-25T15:01:33Z", "session": "64950bfd-f3fc-4676-81c2-5af4e58bb133", "tool": "Bash", "input_summary": {"command": "git diff --stat main.py && echo \"---\" && git diff main.py | grep -E \"^\\+\" | grep -v \"^\\+\\+\\+\" | head -80"}, "ok": null}
+{"ts": "2026-06-25T15:01:39Z", "session": "64950bfd-f3fc-4676-81c2-5af4e58bb133", "tool": "Bash", "input_summary": {"command": "git --no-pager diff main.py"}, "ok": null}
+{"ts": "2026-06-25T15:09:18Z", "session": "9a9f9a71-c626-4931-8a66-055a0b605f5a", "tool": "Read", "input_summary": {"file": "/Users/wuchao/git/miaoCut/pro-gateway/db/schema.sql"}, "ok": null}
+{"ts": "2026-06-25T15:11:56Z", "session": "9a9f9a71-c626-4931-8a66-055a0b605f5a", "tool": "Write", "input_summary": {"file": "/Users/wuchao/git/miaoCut/pro-gateway/db/schema-batch.sql"}, "ok": null}
+{"ts": "2026-06-25T15:12:38Z", "session": "9a9f9a71-c626-4931-8a66-055a0b605f5a", "tool": "Edit", "input_summary": {"file": "/Users/wuchao/git/miaoCut/pro-gpu/app.py"}, "ok": null}
+{"ts": "2026-06-25T15:13:51Z", "session": "9a9f9a71-c626-4931-8a66-055a0b605f5a", "tool": "Edit", "input_summary": {"file": "/Users/wuchao/git/miaoCut/pro-gateway/src/index.js"}, "ok": null}
+{"ts": "2026-06-25T15:14:01Z", "session": "9a9f9a71-c626-4931-8a66-055a0b605f5a", "tool": "Edit", "input_summary": {"file": "/Users/wuchao/git/miaoCut/pro-gateway/src/index.js"}, "ok": null}
+{"ts": "2026-06-25T15:14:55Z", "session": "9a9f9a71-c626-4931-8a66-055a0b605f5a", "tool": "Edit", "input_summary": {"file": "/Users/wuchao/git/miaoCut/pro-gateway/src/index.js"}, "ok": null}
+{"ts": "2026-06-25T15:15:34Z", "session": "9a9f9a71-c626-4931-8a66-055a0b605f5a", "tool": "Edit", "input_summary": {"file": "/Users/wuchao/git/miaoCut/pro-gateway/wrangler.toml"}, "ok": null}
+{"ts": "2026-06-25T15:15:45Z", "session": "9a9f9a71-c626-4931-8a66-055a0b605f5a", "tool": "Edit", "input_summary": {"file": "/Users/wuchao/git/miaoCut/pro-gateway/wrangler.toml"}, "ok": null}
+{"ts": "2026-06-25T15:15:55Z", "session": "9a9f9a71-c626-4931-8a66-055a0b605f5a", "tool": "Bash", "input_summary": {"command": "node --check pro-gateway/src/index.js && echo \"✅ index.js 语法 OK\"; python3 -m py_compile pro-gpu/app.py && echo \"✅ app.py 语法 OK\"; echo \"--- 重跑 Phase 1 冒烟 ---\"; node pro-gateway/test/smoke.mjs 2>&1 | tail -5"}, "ok": null}
+{"ts": "2026-06-25T15:17:28Z", "session": "9a9f9a71-c626-4931-8a66-055a0b605f5a", "tool": "Write", "input_summary": {"file": "/Users/wuchao/git/miaoCut/pro-gateway/test/smoke-batch.mjs"}, "ok": null}
+{"ts": "2026-06-25T15:18:55Z", "session": "9a9f9a71-c626-4931-8a66-055a0b605f5a", "tool": "Bash", "input_summary": {"command": "node pro-gateway/test/smoke-batch.mjs 2>&1 | grep -v \"MODULE_TYPELESS\\|Reparsing\\|eliminate this\\|trace-warnings\""}, "ok": null}
+{"ts": "2026-06-25T15:25:57Z", "session": "9a9f9a71-c626-4931-8a66-055a0b605f5a", "tool": "Write", "input_summary": {"file": "/Users/wuchao/git/miaoCut/batch/index.html"}, "ok": null}
+{"ts": "2026-06-25T15:27:27Z", "session": "9a9f9a71-c626-4931-8a66-055a0b605f5a", "tool": "Write", "input_summary": {"file": "/Users/wuchao/git/miaoCut/batch/batch.js"}, "ok": null}
+{"ts": "2026-06-25T15:27:45Z", "session": "9a9f9a71-c626-4931-8a66-055a0b605f5a", "tool": "Bash", "input_summary": {"command": "node --check batch/batch.js 2>&1 | grep -v \"MODULE_TYPELESS\\|Reparsing\\|eliminate\\|trace-warnings\" ; node --check pro-account.js 2>&1 | grep -v \"MODULE_TYPELESS\\|Reparsing\\|eliminate\\|trace-warnings\" ; echo \"✅ batch.js + pro-account.js 语法 OK(无报错即通过)\""}, "ok": null}
+{"ts": "2026-06-25T15:34:58Z", "session": "9a9f9a71-c626-4931-8a66-055a0b605f5a", "tool": "Write", "input_summary": {"file": "/Users/wuchao/git/miaoCut/docs/feature-pro-deploy.md"}, "ok": null}
+{"ts": "2026-06-26T00:30:39Z", "session": "9a9f9a71-c626-4931-8a66-055a0b605f5a", "tool": "Edit", "input_summary": {"file": "/Users/wuchao/git/miaoCut/docs/feature-pro-deploy.md"}, "ok": null}
diff --git a/.claude/settings.local.json b/.claude/settings.local.json
index 62b7ca4..7d30814 100644
--- a/.claude/settings.local.json
+++ b/.claude/settings.local.json
@@ -46,7 +46,22 @@
"Bash(node --check app.js)",
"Bash(grep -nA8 \"content\" tailwind.config.js)",
"Bash(grep -vE \"^ ?M \\(bn|fil|hi|id|pt-br|ur|zh|ja|ko|fr|de|es|vi|th|ar|ru|tr|it|pl|nl|fa\\)/|how-to-remove|/index\\\\.html$\")",
- "Bash(grep -E \"\\\\.\\(py|js|css|md|json\\)$|scripts/\")"
+ "Bash(grep -E \"\\\\.\\(py|js|css|md|json\\)$|scripts/\")",
+ "Bash(python3 /private/tmp/gen_nb.py)",
+ "Bash(python3 -c \"import json,sys; nb=json.load\\(open\\('benchmark/miaocut_gpu_benchmark.ipynb'\\)\\); print\\('valid json, nbformat', nb['nbformat'], 'cells', len\\(nb['cells']\\)\\)\")",
+ "Bash(python3 -)",
+ "Bash(python3 -c \"import json,ast; nb=json.load\\(open\\('benchmark/miaocut_gpu_benchmark.ipynb'\\)\\); [ast.parse\\('\\\\n'.join\\(l for l in ''.join\\(c['source']\\).splitlines\\(\\) if not l.lstrip\\(\\).startswith\\('!'\\)\\)\\) for c in nb['cells'] if c['cell_type']=='code']; print\\('valid + code OK'\\)\")",
+ "Bash(python3 -c \"import json,ast; nb=json.load\\(open\\('benchmark/miaocut_gpu_benchmark.ipynb'\\)\\); [ast.parse\\('\\\\n'.join\\(l for l in ''.join\\(c['source']\\).splitlines\\(\\) if not l.lstrip\\(\\).startswith\\('!'\\)\\)\\) for c in nb['cells'] if c['cell_type']=='code']; print\\('notebook 已更新默认配置为 matting,校验通过'\\)\")",
+ "Bash(python3 -m py_compile pro-gpu/app.py)",
+ "Bash(node --check pro-gateway/src/index.js)",
+ "Bash(node pro-gateway/test/smoke.mjs)",
+ "WebSearch",
+ "Bash(python -m py_compile main.py)",
+ "Bash(python -)",
+ "Bash(git --no-pager diff main.py)",
+ "Bash(node pro-gateway/test/smoke-batch.mjs)",
+ "Bash(node --check batch/batch.js)",
+ "Bash(node --check pro-account.js)"
]
}
}
diff --git a/.gitignore b/.gitignore
index fadfcc3..39d2acd 100644
--- a/.gitignore
+++ b/.gitignore
@@ -49,3 +49,14 @@ models/*.bak
.claude
.agent
.playwright-cli
+ads.txt
+b27e253973b44d85af293d4f007fba0c.txt
+googlebbfb4abe1fc3d9df.html
+miaocut-seo-design-doc.md
+old-photo-restoration-design.md
+quantize_birefnet.ipynb
+docs/
+.claude/*
+.agents/
+scripts/
+benchmark
diff --git a/Dockerfile b/Dockerfile
index 39dd087..5f34fc3 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -50,9 +50,18 @@ download_url_to_file('https://github.com/enesmsahin/simple-lama-inpainting/relea
# ============================================================
FROM python:3.11-slim
-# 系统依赖:MediaPipe/OpenCV 运行时需要 libGL/libglib。
+# 系统依赖:
+# libgl1 / libglib2.0-0 —— MediaPipe / OpenCV 运行时依赖
+# libjemalloc2 —— 替换 glibc 的默认 malloc;用它主要有两个收益:
+# 1) 内存碎片显著小于 glibc ptmalloc,同样负载下 RSS 峰值/steady-state 更低,
+# 对 cgroup 内存受限(HF Space 2 vCPU, 16GB 硬顶)尤其有价值。
+# 2) dirty/muzzy decay 机制会真正把空闲页归还给 OS,不像 glibc 那样即使 malloc_trim
+# 也常常留在进程里——这是我们过去靠 _malloc_trim + gc.collect 兜底的问题的更彻底解法。
+# ⚠️ 注意 jemalloc 只解"分配器 → OS"这一层;ORT 自己的 arena("ORT → 分配器")仍需靠
+# main.py 的 memory.enable_memory_arena_shrinkage 归还——两者互补,不要因为上了 jemalloc
+# 就关掉 ENABLE_ARENA_SHRINKAGE,会让 sharp + fur 两个 session 的 arena 叠加冲顶 OOM。
RUN apt-get update && \
- apt-get install -y --no-install-recommends libgl1 libglib2.0-0 && \
+ apt-get install -y --no-install-recommends libgl1 libglib2.0-0 libjemalloc2 && \
useradd -m -u 1000 user && \
mkdir -p /home/user/app /data && \
chown -R user:user /home/user/app /data && \
@@ -91,10 +100,23 @@ COPY --chown=user:user models/birefnet-general-lite-768.onnx /opt/miaocut-models
# 模型已内置到镜像;rembg 模型缺失时会自动退到可写缓存目录。
# big-lama(去水印 inpainting 模型)由上面 builder 预置到 /opt/miaocut-models/big-lama.pt,
# 用 LAMA_MODEL 指定后 SimpleLama 直接加载本地文件,容器重启不再去 GitHub 重下 196MB。
+#
+# ---- jemalloc 相关(见上文 libjemalloc2 系统依赖注释)----
+# LD_PRELOAD 让所有子进程(含 uvicorn 主进程、SimpleLama 的 ProcessPool worker)都跑 jemalloc。
+# MALLOC_CONF 是 jemalloc 的 tuning,取的是 PyTorch 官方 Docker 也在用的一组保守值:
+# background_thread:true —— 后台线程做 purge,避免 malloc/free 热路径承担 decay 成本
+# metadata_thp:auto —— 对 jemalloc 自己的元数据用 THP,减少 TLB miss
+# dirty_decay_ms:5000 —— 5s 内不再用的 dirty page 归还给 OS
+# muzzy_decay_ms:5000 —— 同上,第二层 muzzy state(jemalloc 的 lazy purge 分层)
+# 万一 jemalloc 在某个 Space 上表现异常,把这两行注掉重建镜像即可回退到 glibc;
+# MALLOC_ARENA_MAX / MALLOC_TRIM_THRESHOLD_ 是 glibc 特有的 tuning,jemalloc 加载后被自动忽略,
+# 保留是为了让"关掉 jemalloc 回退 glibc"的场景仍然拿到之前调过的 glibc 参数。
ENV PORT=7860 \
U2NET_HOME=/opt/miaocut-models \
LAMA_MODEL=/opt/miaocut-models/big-lama.pt \
DATA_DIR=/data \
+ LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libjemalloc.so.2 \
+ MALLOC_CONF="background_thread:true,metadata_thp:auto,dirty_decay_ms:5000,muzzy_decay_ms:5000" \
MALLOC_ARENA_MAX=2 \
MALLOC_TRIM_THRESHOLD_=131072
diff --git a/Dockerfile.openvino b/Dockerfile.openvino
new file mode 100644
index 0000000..686c0a3
--- /dev/null
+++ b/Dockerfile.openvino
@@ -0,0 +1,117 @@
+# ============================================================
+# Dockerfile.openvino — OpenVINO EP variant (A/B image for one Space)
+# ============================================================
+# 与主 Dockerfile 唯一的差异(两处):
+# 1) builder 阶段:装完 requirements.txt 后,把 rembg[cpu] 顺带装进来的 onnxruntime 移除,
+# 再装 onnxruntime-openvino。这个 wheel 里 bundle 了 OpenVINO 2025.4.1 CPU 内核,
+# 同时保留标准 CPUExecutionProvider 作为兜底。
+# ⚠️ 不能把两个 wheel 共存:它们共享 top-level 模块名 `onnxruntime`,共存时
+# import 结果依赖安装顺序,行为不可预测——必须先 rm 掉旧的 site-packages 目录
+# 再装新的(pip uninstall 在 --prefix 模式下不工作,故用 rm)。
+# 2) 运行时 ENV 追加 USE_OPENVINO_EP=1,让 main.py 的 _resolve_providers() 首选
+# OpenVINOExecutionProvider,CPU EP 依然作为兜底(不可用时静默回退)。
+#
+# 其余(jemalloc、模型预置、big-lama 预下载、其他 ENV 变量)与主 Dockerfile 完全一致。
+#
+# ---- 构建 & 使用 ----
+# docker build -t miaocut-api-openvino -f Dockerfile.openvino .
+# # Space 端:把 3 个 Space 中的一个(e.g. miao_cut3)换成这个镜像做灰度,
+# # 另外两个继续跑默认 Dockerfile 做对照。
+#
+# ---- 期望收益(待 Space 端验证)----
+# BiRefNet-lite(Swin Transformer 结构)在 Xeon 8375C(含 AVX-512 VNNI)上,
+# OpenVINO CPU 内核对比 onnxruntime MLAS 通常快 1.5~3× at FP32。见
+# docs/cutout-perf-followups.md 的 §1 里的完整测量协议和决策规则。
+#
+# ---- 验证 checklist(部署后第一件事)----
+# 看容器启动日志。main.py 的 _log_session_providers 会打印每个 session 用的 EP:
+# Session 'sharp/birefnet-general-lite-768' providers: ['OpenVINOExecutionProvider', 'CPUExecutionProvider']
+# 如果看到 providers 只有 CPUExecutionProvider,说明 OpenVINO EP 未生效
+# (可能是 onnxruntime-openvino 装错了、USE_OPENVINO_EP=0 被下游覆盖了、或 EP 初始化失败),
+# 参考 docs/cutout-perf-followups.md 的排错步骤。
+#
+# ---- 回退 ----
+# 删除本文件、切回默认 Dockerfile 重建镜像即可。main.py 里没有任何 OpenVINO 专属代码——
+# `_resolve_providers()` 在 OpenVINO EP 不可用时自动回退到纯 CPU,完全对称。
+# ============================================================
+
+FROM python:3.11-slim AS builder
+
+WORKDIR /build
+
+# 构建阶段也会 import rembg/mediapipe/OpenCV 来预下载模型;缺 libGL 时 python -c 报错。
+RUN apt-get update && \
+ apt-get install -y --no-install-recommends libgl1 libglib2.0-0 && \
+ rm -rf /var/lib/apt/lists/*
+
+# 先装完常规依赖(复用主 Dockerfile 的 requirements.txt,避免依赖表分叉维护)
+COPY requirements.txt .
+RUN pip install --no-cache-dir --prefix=/install -r requirements.txt && \
+ # rembg[cpu] 会连带装 onnxruntime;OpenVINO 变体不能与之共存(同一 top-level 模块名)
+ # pip uninstall 在 --prefix 模式下不工作,只能物理 rm site-packages 里的 onnxruntime 目录
+ rm -rf /install/lib/python3.11/site-packages/onnxruntime* && \
+ # 装 onnxruntime-openvino:wheel 里 bundle 了 OpenVINO 2025.4.1 CPU/GPU 库(Linux 单独 84MB wheel),
+ # 不需要在系统层单独装 openvino。pin 到 >=1.20,<2.0 覆盖当前 stable 且避免大版本跳跃。
+ pip install --no-cache-dir --prefix=/install "onnxruntime-openvino>=1.20,<2.0"
+
+# 模型预下载:与主 Dockerfile 完全一致
+RUN mkdir -p /model-cache && \
+ U2NET_HOME=/model-cache \
+ PYTHONPATH=/install/lib/python3.11/site-packages \
+ python -c "from rembg.sessions import sessions_class; \
+names=['isnet-general-use','birefnet-general-lite']; \
+[print(next(sc for sc in sessions_class if sc.name()==n).download_models()) for n in names]"
+
+# big-lama.pt(去水印 inpainting 模型,196MB)也预下载,避免容器重启后首个去水印请求触发 GitHub 下载
+RUN PYTHONPATH=/install/lib/python3.11/site-packages \
+ python -c "from torch.hub import download_url_to_file; \
+download_url_to_file('https://github.com/enesmsahin/simple-lama-inpainting/releases/download/v0.1.0/big-lama.pt', '/model-cache/big-lama.pt')"
+
+# ============================================================
+FROM python:3.11-slim
+
+# 与主 Dockerfile 同一套 apt 依赖 + jemalloc(见主 Dockerfile 该行注释了解每个包的用途)
+RUN apt-get update && \
+ apt-get install -y --no-install-recommends libgl1 libglib2.0-0 libjemalloc2 && \
+ useradd -m -u 1000 user && \
+ mkdir -p /home/user/app /data && \
+ chown -R user:user /home/user/app /data && \
+ rm -rf /var/lib/apt/lists/*
+
+WORKDIR /home/user/app
+
+# 从 builder 拷贝已安装的 Python 包
+COPY --from=builder /install /usr/local
+COPY --from=builder --chown=user:user /model-cache /opt/miaocut-models
+
+# 拷贝后端运行必需文件。前端由 Cloudflare Pages 托管,不放进 Space 镜像。
+COPY --chown=user:user main.py .
+
+# INT8 量化的 BiRefNet-lite 模型(详见主 Dockerfile 该行注释)
+COPY --chown=user:user models/birefnet-general-lite-int8.onnx /opt/miaocut-models/
+
+# fur 档 BiRefNet_lite-matting 模型(详见主 Dockerfile 该行注释)
+COPY --chown=user:user models/birefnet-lite-matting.onnx /opt/miaocut-models/
+
+# sharp 折中档 BiRefNet-general-lite @ 768² 模型(详见主 Dockerfile 该行注释)
+COPY --chown=user:user models/birefnet-general-lite-768.onnx /opt/miaocut-models/
+
+# ---- ENV:与主 Dockerfile 完全一致,只是最后加了 USE_OPENVINO_EP=1 ----
+# jemalloc / MALLOC_CONF 的详细解释见主 Dockerfile 同名 ENV 块。
+# USE_OPENVINO_EP=1 让 main.py 的 _resolve_providers() 首选 OpenVINOExecutionProvider,
+# CPU EP 依然在 providers 列表末尾兜底——两者共存互不干扰。
+ENV PORT=7860 \
+ U2NET_HOME=/opt/miaocut-models \
+ LAMA_MODEL=/opt/miaocut-models/big-lama.pt \
+ DATA_DIR=/data \
+ LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libjemalloc.so.2 \
+ MALLOC_CONF="background_thread:true,metadata_thp:auto,dirty_decay_ms:5000,muzzy_decay_ms:5000" \
+ MALLOC_ARENA_MAX=2 \
+ MALLOC_TRIM_THRESHOLD_=131072 \
+ USE_OPENVINO_EP=1
+
+USER user
+
+# 单 worker,不要加 --workers!(限流和并发控制在进程内存里)
+EXPOSE 7860
+CMD ["python", "main.py"]
diff --git a/account/credits/index.html b/account/credits/index.html
new file mode 100644
index 0000000..7431d5c
--- /dev/null
+++ b/account/credits/index.html
@@ -0,0 +1,182 @@
+
+
+
+
+
+
+
+ Your Credits | MiaoCut
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Sign in to MiaoCut
+ Access your credits, history, and top-ups.
+
+
+
or
+
+
+
+
+
+
+
+
+
+
Your credits
+
+
+
+
+
+
+
+
+ Transaction history
+
+
+
+
+ | When |
+ Reason |
+ Change |
+ Balance |
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/alternatives/remove-bg/index.html b/alternatives/remove-bg/index.html
index 0db51f1..f07f749 100644
--- a/alternatives/remove-bg/index.html
+++ b/alternatives/remove-bg/index.html
@@ -97,7 +97,7 @@
]
}
-
+
diff --git a/app.js b/app.js
index 07ca966..6f05f8a 100644
--- a/app.js
+++ b/app.js
@@ -1101,25 +1101,12 @@
// 埋点 (Umami custom events)
// ============================================================
// umami 脚本异步加载,未就绪/被广告拦截时直接 no-op,不让分析故障影响主流程。
- function track(name, data) {
+ function track(name) {
if (typeof umami === 'undefined') return;
try {
- if (data) umami.track(name, data); else umami.track(name);
+ umami.track(name);
} catch (_) { /* analytics 失败不影响业务 */ }
}
- function sizeBucket(bytes) {
- if (bytes < 500 * 1024) return '<500K';
- if (bytes < 2 * 1024 * 1024) return '500K-2M';
- if (bytes < 5 * 1024 * 1024) return '2M-5M';
- return '5M-10M';
- }
- function durationBucket(ms) {
- const s = ms / 1000;
- if (s < 2) return '<2s';
- if (s < 5) return '2-5s';
- if (s < 10) return '5-10s';
- return '>10s';
- }
// ============================================================
// i18n 切换
@@ -1222,7 +1209,7 @@
const from = currentLang;
const to = e.target.value;
if (to === currentLang) return;
- track('lang-switched', { from, to });
+ track('lang-switched');
const target = alternateUrlFor(to) + window.location.search + window.location.hash;
// 用 localStorage 记住偏好,下次直接打开根域名时可以让用户感知(虽然不再用作语言判定的源)
localStorage.setItem('lang', to);
@@ -1265,7 +1252,7 @@
currentProfile = next;
localStorage.setItem('cutoutProfile', next);
updateProfileButtons();
- track('profile-switched', { from, to: next });
+ track('profile-switched');
});
});
updateProfileButtons();
@@ -1369,7 +1356,7 @@
modal.classList.remove('hidden');
modal.classList.add('flex');
document.body.classList.add('overflow-hidden');
- track('waitlist-opened', { page: window.MIAOCUT_PAGE_KEY || 'unknown', lang: currentLang, variant: currentVariant });
+ track('waitlist-opened');
setTimeout(() => emailInput.focus(), 0);
}
openWaitlistModal = openModal; // 暴露给外部(抠图 429 调用)
@@ -1392,20 +1379,20 @@
});
modal.querySelector('[data-batch-backdrop]').addEventListener('click', () => {
- track('batch-door-dismissed', { page: window.MIAOCUT_PAGE_KEY || 'unknown', lang: currentLang, method: 'backdrop' });
+ track('batch-door-dismissed');
closeModal();
});
closeButtons.forEach(button => {
button.addEventListener('click', () => {
- track('batch-door-dismissed', { page: window.MIAOCUT_PAGE_KEY || 'unknown', lang: currentLang, method: 'button' });
+ track('batch-door-dismissed');
closeModal();
});
});
document.addEventListener('keydown', (event) => {
if (event.key !== 'Escape' || modal.classList.contains('hidden')) return;
- track('batch-door-dismissed', { page: window.MIAOCUT_PAGE_KEY || 'unknown', lang: currentLang, method: 'escape' });
+ track('batch-door-dismissed');
closeModal();
});
@@ -1442,7 +1429,7 @@
}),
});
if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
- track('batch-waitlist-submitted', { page: window.MIAOCUT_PAGE_KEY || 'unknown', lang: currentLang });
+ track('batch-waitlist-submitted');
formWrap.classList.add('hidden');
thanksEl.classList.remove('hidden');
const doneButton = modal.querySelector('#batch-thanks-close');
@@ -1450,7 +1437,7 @@
} catch (error) {
console.warn('[MiaoCut] Batch waitlist submit failed:', error);
setError(t('batchModalError'));
- track('batch-waitlist-failed', { page: window.MIAOCUT_PAGE_KEY || 'unknown', lang: currentLang });
+ track('batch-waitlist-failed');
} finally {
submitButton.disabled = false;
submitButton.textContent = t('batchModalSubmit');
@@ -1603,9 +1590,19 @@
function uploadWithProgress(formData) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
- const url = `${API_BASE}/api/remove-background?profile=${encodeURIComponent(currentProfile)}`;
+ // 登录用户走 Pro 网关(GPU,按 profile 扣分);匿名走免费网关(CPU,含 localhost dev)。
+ // MiaoCutPro 未加载或未登录时,url 与鉴权头与原来完全一致,零回归。
+ const pro = window.MiaoCutPro;
+ const loggedIn = !!(pro && pro.isLoggedIn && pro.isLoggedIn());
+ const url = loggedIn
+ ? pro.removeBackgroundUrl(currentProfile)
+ : `${API_BASE}/api/remove-background?profile=${encodeURIComponent(currentProfile)}`;
xhr.open('POST', url);
xhr.responseType = 'blob';
+ // 登录用户带 Authorization(Supabase JWT);匿名为空
+ if (loggedIn && pro.authHeader) {
+ for (const [k, v] of Object.entries(pro.authHeader())) xhr.setRequestHeader(k, v);
+ }
xhr.upload.onprogress = (e) => {
if (!e.lengthComputable) return;
@@ -1713,11 +1710,11 @@
document.body.removeChild(a);
}
- function autoDownloadTransparentResult(file, blob, page) {
+ function autoDownloadTransparentResult(file, blob) {
const url = URL.createObjectURL(blob);
downloadUrl(url, `${basenameFromFile(file)}_transparent.png`);
setTimeout(() => URL.revokeObjectURL(url), 1000);
- track('transparent-download', { page, mode: 'auto' });
+ track('transparent-download');
}
function bgLabelKey(bg) {
@@ -1999,7 +1996,7 @@
const suffix = shapeOverride || resultState.config.downloadSuffix;
downloadUrl(url, `${resultState.basename}_${suffix}.png`);
setTimeout(() => URL.revokeObjectURL(url), 1000);
- track('edited-download', { page: resultState.mode, bg: resultState.bg, size: resultState.size, shape: shapeOverride || resultState.shape });
+ track('edited-download');
}, 'image/png');
}
@@ -2027,7 +2024,7 @@
const scaleInput = document.getElementById('editor-bg-scale');
if (scaleInput) scaleInput.value = '100';
renderPreview();
- track('background-image-uploaded', { page: resultState.mode, type: file.type.replace('image/', '') });
+ track('background-image-uploaded');
}
function renderResultEditor() {
@@ -2229,7 +2226,7 @@
const shadowSelect = document.getElementById('editor-shadow');
if (shadowSelect) shadowSelect.value = resultState.shadow;
renderPreview();
- track('amazon-preset-applied', { page: resultState.mode });
+ track('amazon-preset-applied');
});
}
const customColor = document.getElementById('editor-custom-color');
@@ -2276,7 +2273,7 @@
});
document.getElementById('download-transparent').addEventListener('click', () => {
downloadUrl(resultState.cutoutUrl, `${resultState.basename}_transparent.png`);
- track('transparent-download', { page: resultState.mode });
+ track('transparent-download');
});
const downloadEdited = document.getElementById('download-edited');
if (downloadEdited) downloadEdited.addEventListener('click', () => downloadCurrentVersion());
@@ -2341,18 +2338,18 @@
async function handleFiles(files) {
const file = files[0];
+ // 选图即预热 Beam(登录用户):把 GPU 冷启动藏进后续「压缩+上传」窗口;匿名时是 no-op。
+ window.MiaoCutPro?.prewarm?.();
+
if (!ALLOWED_TYPES.includes(file.type)) {
- track('format-rejected', { type: file && file.type ? file.type : 'unknown' });
+ track('format-rejected');
alert(t('alertSize'));
return;
}
console.log(`[MiaoCut] 原始图片: ${file.name} (${(file.size / 1024 / 1024).toFixed(2)} MB)`);
- const fileExt = file.type.replace('image/', '');
- // 子页打点带 page 维度,便于在 Umami 后台分析"哪个 landing page 转化最高"
const page = (window.MIAOCUT_PAGE_KEY || 'home');
- track('upload-started', { type: fileExt, size: sizeBucket(file.size), profile: currentProfile, page });
- const startedAt = performance.now();
+ track('upload-started');
cleanupResultState();
dropzoneContent.classList.add('pointer-events-none');
@@ -2371,15 +2368,10 @@
formData.append('file', uploadFile);
const blob = await uploadWithProgress(formData);
- track('cutout-success', {
- type: fileExt,
- size: sizeBucket(file.size),
- duration: durationBucket(performance.now() - startedAt),
- page,
- });
+ track('cutout-success');
if (page === 'home') {
- autoDownloadTransparentResult(file, blob, page);
+ autoDownloadTransparentResult(file, blob);
shouldResetDropzone = true;
} else {
await showResultEditor(file, uploadFile, blob);
@@ -2393,21 +2385,26 @@
if (error.status === 429) {
if (error.scope === 'day') {
// 今日免费额度用尽(明天重置)→ 引导升级 Pro(弹预约弹窗)
- track('cutout-rate-limited', { type: fileExt, page, scope: 'day' });
+ track('cutout-rate-limited');
resetDropzone();
if (typeof openWaitlistModal === 'function') openWaitlistModal('rateDay');
} else {
// 手速太快(minute):当天还有额度,给温和提示 + 可选 Pro 批量入口。
// 保留提示(不让 finally 的延时 reset 把它清掉),用户读完可点 Pro 或重新拖图。
shouldResetDropzone = false;
- track('cutout-rate-limited', { type: fileExt, page, scope: 'minute' });
+ track('cutout-rate-limited');
showRateBurstNotice(Math.max(1, Math.round(error.retryAfter || 30)));
}
return;
}
- const reason = error.status ? `http-${error.status}`
- : (error.kind || (error.message && /压缩|WebP/.test(error.message) ? 'compress' : 'unknown'));
- track('cutout-failed', { type: fileExt, reason, page });
+ // 积分不足(登录付费路径专有)→ 引导去充值
+ if (error.status === 402) {
+ track('cutout-insufficient-credits');
+ resetDropzone();
+ window.location.href = '/pricing/';
+ return;
+ }
+ track('cutout-failed');
// 用 DOM 拼装而不是 innerHTML 拼字符串,防止 error.message 里若含 HTML 被当标签执行(XSS)
dropzoneContent.innerHTML = '';
const icon = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
diff --git a/batch/batch.js b/batch/batch.js
new file mode 100644
index 0000000..d62ef7c
--- /dev/null
+++ b/batch/batch.js
@@ -0,0 +1,310 @@
+// =============================================================================
+// MiaoCut 批量去背景 —— 前端流程
+// 建任务 → 逐张直传 R2(预签名) → 开始(冻结+入队) → 轮询进度 → ZIP 下载
+// 复用 pro-account.js 的登录态;所有 Pro API 调用走 MiaoCutPro.proFetch(自动带 JWT)。
+// =============================================================================
+import MiaoCutPro from "/pro-account.js";
+
+const CREDIT_COST = { fast: 1, fur: 2 };
+const MAX_IMAGES = 200;
+const ALLOWED = ["image/png", "image/jpeg", "image/webp"];
+
+const $ = (id) => document.getElementById(id);
+const els = {
+ balance: $("balance"),
+ loginGate: $("login-gate"),
+ btnGoogle: $("btn-google"),
+ btnEmail: $("btn-email"),
+ loginEmail: $("login-email"),
+ loginMsg: $("login-msg"),
+ tool: $("tool"),
+ profileToggle: $("profile-toggle"),
+ dropzone: $("dropzone"),
+ fileInput: $("file-input"),
+ fileList: $("file-list"),
+ costLine: $("cost-line"),
+ btnStart: $("btn-start"),
+ progress: $("progress"),
+ progressText: $("progress-text"),
+ progressBar: $("progress-bar"),
+ imageGrid: $("image-grid"),
+ results: $("results"),
+ resultsSummary: $("results-summary"),
+ resultsMsg: $("results-msg"),
+ btnZip: $("btn-zip"),
+};
+
+let profile = "fast";
+let files = []; // 选中的 File[]
+let chips = {}; // image_id → 缩略图 chip 元素
+let lastImages = []; // 最近一次状态的 images(给 ZIP 用)
+
+const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
+
+// ---------- 登录态 ----------
+async function render() {
+ const logged = MiaoCutPro.isLoggedIn();
+ els.loginGate.classList.toggle("hidden", logged);
+ els.tool.classList.toggle("hidden", !logged);
+ if (logged) {
+ try {
+ const b = await MiaoCutPro.balance();
+ els.balance.innerHTML = `Balance: ${b.total} credits`;
+ } catch (_) { /* ignore */ }
+ } else {
+ els.balance.textContent = "";
+ }
+}
+
+els.btnGoogle.addEventListener("click", async () => {
+ try { await MiaoCutPro.signInWithGoogle(window.location.href); }
+ catch (e) { els.loginMsg.textContent = e.message === "auth_unavailable" ? "登录暂未配置(Supabase)。" : "登录失败,请重试。"; }
+});
+els.btnEmail.addEventListener("click", async () => {
+ const email = els.loginEmail.value.trim();
+ if (!email) return;
+ try {
+ await MiaoCutPro.signInWithEmail(email, window.location.href);
+ els.loginMsg.textContent = "Magic link sent — check your email.";
+ } catch (e) {
+ els.loginMsg.textContent = e.message === "auth_unavailable" ? "登录暂未配置(Supabase)。" : "Failed to send, try again.";
+ }
+});
+
+// ---------- profile 切换 ----------
+els.profileToggle.querySelectorAll("button").forEach((b) => {
+ b.addEventListener("click", () => {
+ profile = b.dataset.profile;
+ els.profileToggle.querySelectorAll("button").forEach((x) => x.classList.toggle("active", x === b));
+ updateCost();
+ });
+});
+
+// ---------- 选图 ----------
+els.dropzone.addEventListener("click", () => els.fileInput.click());
+els.fileInput.addEventListener("change", () => addFiles(els.fileInput.files));
+["dragover", "dragenter"].forEach((ev) =>
+ els.dropzone.addEventListener(ev, (e) => { e.preventDefault(); els.dropzone.classList.add("drag"); }));
+["dragleave", "drop"].forEach((ev) =>
+ els.dropzone.addEventListener(ev, (e) => { e.preventDefault(); els.dropzone.classList.remove("drag"); }));
+els.dropzone.addEventListener("drop", (e) => addFiles(e.dataTransfer.files));
+
+function addFiles(fileList) {
+ for (const f of fileList) {
+ if (ALLOWED.includes(f.type) && files.length < MAX_IMAGES) files.push(f);
+ }
+ els.fileInput.value = "";
+ els.fileList.textContent = files.length ? `${files.length} image${files.length > 1 ? "s" : ""} selected` : "";
+ updateCost();
+}
+
+function updateCost() {
+ const has = files.length > 0;
+ els.btnStart.classList.toggle("hidden", !has);
+ els.costLine.classList.toggle("hidden", !has);
+ if (has) {
+ const cost = files.length * CREDIT_COST[profile];
+ els.costLine.innerHTML = `${files.length} × ${CREDIT_COST[profile]} = ${cost} credits · failed images are not charged`;
+ }
+}
+
+// ---------- 主流程 ----------
+els.btnStart.addEventListener("click", start);
+
+async function start() {
+ if (!files.length) return;
+ els.btnStart.disabled = true;
+ els.tool.classList.add("hidden");
+ els.progress.classList.remove("hidden");
+ els.results.classList.add("hidden");
+
+ try {
+ // 1) 建任务 → 拿到每图的预签名 PUT URL
+ setProgress("Preparing…", 0.02);
+ const create = await MiaoCutPro.proFetch("/v1/batch", {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify({ profile, files: files.map((f) => ({ name: f.name, size: f.size })) }),
+ });
+ if (create.status === 402) return paywall();
+ if (!create.ok) throw new Error(`create_${create.status}`);
+ const job = await create.json();
+
+ // 建缩略图 chips(按返回顺序对应 files)
+ els.imageGrid.innerHTML = "";
+ chips = {};
+ job.uploads.forEach((up, i) => {
+ const el = document.createElement("div");
+ el.className = "chip";
+ el.dataset.id = up.image_id;
+ el.innerHTML = `
+
+
+
+
+ …
+ `;
+ els.imageGrid.appendChild(el);
+ chips[up.image_id] = el;
+ });
+
+ // 2) 逐张直传 R2(限并发 4,绕过 Worker / CF 100MB)
+ let uploaded = 0;
+ await runPool(job.uploads, async (up, i) => {
+ const el = chips[up.image_id];
+ if (el) el.querySelector(".spinner").style.display = "block";
+ await putToR2(files[i], up.put_url);
+ uploaded++;
+ setBadge(up.image_id, "↑", "");
+ setProgress(`Uploading ${uploaded}/${job.uploads.length}`, (uploaded / job.uploads.length) * 0.4);
+ }, 4);
+
+ // 3) 开始 → 冻结积分 + 入队 Beam
+ setProgress("Starting…", 0.42);
+ const startRes = await MiaoCutPro.proFetch(`/v1/batch/${job.job_id}/start`, { method: "POST" });
+ if (startRes.status === 402) return paywall();
+ if (!startRes.ok) throw new Error(`start_${startRes.status}`);
+
+ // 4) 轮询进度
+ await poll(job.job_id, job.uploads.length);
+ } catch (e) {
+ setProgress("Something went wrong: " + e.message, 1, true);
+ els.btnStart.disabled = false;
+ }
+}
+
+async function poll(jobId, total) {
+ for (;;) {
+ await sleep(2000);
+ const res = await MiaoCutPro.proFetch(`/v1/batch/${jobId}`);
+ if (!res.ok) { await sleep(2000); continue; }
+ const data = await res.json();
+ lastImages = data.images || [];
+ for (const im of lastImages) {
+ if (im.status === "done") {
+ setBadge(im.image_id, "✓", "done");
+ const el = chips[im.image_id];
+ if (el && !el.dataset.done) {
+ el.dataset.done = "1";
+ el.dataset.url = im.download_url;
+ const resImg = el.querySelector(".result");
+ resImg.src = im.download_url;
+ resImg.style.display = "block";
+ el.querySelector(".spinner").style.display = "none";
+ }
+ }
+ else if (im.status === "failed") {
+ setBadge(im.image_id, "✕", "failed");
+ const el = chips[im.image_id];
+ if (el) el.querySelector(".spinner").style.display = "none";
+ }
+ }
+ const resolved = data.succeeded + data.failed;
+ setProgress(`Processing… ${resolved}/${total} done`, 0.4 + (resolved / total) * 0.6);
+ if (data.done) {
+ return showResults(data);
+ }
+ }
+}
+
+function showResults(data) {
+ els.progress.classList.add("hidden");
+ els.results.classList.remove("hidden");
+ els.resultsSummary.textContent = `Done — ${data.succeeded} succeeded, ${data.failed} failed`;
+ els.resultsMsg.textContent = data.failed ? "Failed images were not charged." : "";
+ els.btnZip.disabled = data.succeeded === 0;
+ render(); // 刷新余额
+}
+
+els.btnZip.addEventListener("click", async () => {
+ const done = lastImages.filter((im) => im.status === "done" && im.download_url);
+ if (!done.length) return;
+ els.btnZip.disabled = true;
+ els.resultsMsg.textContent = "Packing ZIP…";
+ try {
+ const zip = new JSZip();
+ let n = 0;
+ await runPool(done, async (im) => {
+ const blob = await (await fetch(im.download_url)).blob();
+ const base = (im.filename || "image").replace(/\.[^.]+$/, "");
+ zip.file(`${base}_removed.webp`, blob);
+ els.resultsMsg.textContent = `Packing ZIP… ${++n}/${done.length}`;
+ }, 4);
+ const out = await zip.generateAsync({ type: "blob" });
+ const a = document.createElement("a");
+ a.href = URL.createObjectURL(out);
+ a.download = "miaocut_batch.zip";
+ document.body.appendChild(a); a.click(); a.remove();
+ setTimeout(() => URL.revokeObjectURL(a.href), 3000);
+ els.resultsMsg.textContent = "";
+ } catch (e) {
+ els.resultsMsg.textContent = "ZIP failed: " + e.message;
+ } finally {
+ els.btnZip.disabled = false;
+ }
+});
+
+// ---------- 工具 ----------
+function putToR2(file, url) {
+ return new Promise((resolve, reject) => {
+ const xhr = new XMLHttpRequest();
+ xhr.open("PUT", url);
+ xhr.onload = () => (xhr.status >= 200 && xhr.status < 300 ? resolve() : reject(new Error("upload_" + xhr.status)));
+ xhr.onerror = () => reject(new Error("upload_network"));
+ xhr.send(file);
+ });
+}
+
+async function runPool(items, worker, concurrency = 4) {
+ let i = 0;
+ const run = async () => { while (i < items.length) { const idx = i++; await worker(items[idx], idx); } };
+ await Promise.all(Array.from({ length: Math.min(concurrency, items.length) }, run));
+}
+
+function setBadge(imageId, text, cls) {
+ const el = chips[imageId];
+ if (!el) return;
+ el.className = "chip" + (cls ? " " + cls : "");
+ const b = el.querySelector(".badge");
+ if (b) b.textContent = text;
+}
+
+function setProgress(text, frac, isErr) {
+ els.progressText.textContent = text;
+ els.progressText.style.color = isErr ? "#dc2626" : "";
+ els.progressBar.style.width = Math.max(0, Math.min(1, frac)) * 100 + "%";
+}
+
+function paywall() {
+ setProgress("Not enough credits.", 1, true);
+ setTimeout(() => { window.location.href = "/pricing/"; }, 1200);
+}
+
+// ---------- 启动 ----------
+MiaoCutPro.init().then(render).catch(() => render());
+MiaoCutPro.onAuthChange(render);
+
+// ---------- Lightbox ----------
+const lightbox = document.getElementById("lightbox");
+const lightboxImg = document.getElementById("lightbox-img");
+const lightboxClose = document.getElementById("lightbox-close");
+
+els.imageGrid.addEventListener("click", (e) => {
+ const chip = e.target.closest(".chip.done");
+ if (!chip || !chip.dataset.url) return;
+ lightboxImg.src = chip.dataset.url;
+ lightbox.classList.add("show");
+});
+
+function closeLightbox() {
+ lightbox.classList.remove("show");
+ setTimeout(() => { lightboxImg.src = ""; }, 300);
+}
+
+lightboxClose.addEventListener("click", closeLightbox);
+lightbox.addEventListener("click", (e) => {
+ if (e.target === lightbox) closeLightbox();
+});
+document.addEventListener("keydown", (e) => {
+ if (e.key === "Escape" && lightbox.classList.contains("show")) closeLightbox();
+});
diff --git a/batch/index.html b/batch/index.html
new file mode 100644
index 0000000..d867c67
--- /dev/null
+++ b/batch/index.html
@@ -0,0 +1,430 @@
+
+
+
+
+
+
+
+ Batch Background Remover — MiaoCut
+
+
+
+
+
+
+
+
+
+
+
Batch Background Remover
+
Upload many images, remove backgrounds in bulk, download all as a ZIP.
+
+
+
+
Sign in to use batch processing.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
![]()
+
+
+
+
+
+
\ No newline at end of file
diff --git a/bn/id-photo-maker/index.html b/bn/id-photo-maker/index.html
index dc26039..7024073 100644
--- a/bn/id-photo-maker/index.html
+++ b/bn/id-photo-maker/index.html
@@ -184,7 +184,7 @@
]
}
-
+
diff --git a/bn/index.html b/bn/index.html
index a828acd..e3f38d7 100644
--- a/bn/index.html
+++ b/bn/index.html
@@ -8,19 +8,19 @@
+ })();
@@ -232,7 +232,7 @@
源:src/input.css;构建命令:npm run build:css;输出:output.css
改动 index.html 的 class 后必须重新跑 build:css 并把 output.css 一起提交。
-->
-
+
@@ -249,9 +249,11 @@
-
+
-
+
-