家里十几年的扫描件、单据、证件、说明书、孩子的画、老照片……堆在硬盘上等于没有。 ScanDex 先用本地离线 OCR 把它们建成可搜索索引,再给你一个网页界面找图、看图、整理、问 AI。手机电脑浏览器都能用,也能装成 App。
所有 OCR 都在你自己的机器上跑,图片不出本机。
截图里的内容全部是合成的演示数据(一个专门为截图造的假库),不是任何人的真实文件。
每个月一张卡片、2×2 封面缩略图 + 张数,点进去看那个月的全部。
文件名 / 别名 / OCR 全文 / 自动关键词 / 手动标签 / 分组名 / 备注——七路一起搜,多个词是 AND。 排序按相关性:文件名·标签·分组命中 > 自动关键词 > 备注 > OCR 全文,所以"真的身份证"排在"提到身份证的表单"前面。
左右滑 / 方向键翻页,双击进全屏,滚轮或双指缩放 1–6 倍。有原图的还能一键调阅未裁剪的原始扫描。
自动关键词由本地流水线抽取(文字图用 jieba 分词,低文字图用视觉模型识主题),手动标签你自己加。分组是两级可折叠树,能拖拽归类。
描述你要找什么,AI 自己调检索工具、换词重试,用中文回答并附上命中的图。
隐私边界:发给 AI 平台的只有候选图的 OCR 文字,图片本身不上传。
拖放图片 / PDF / zip,选组、附标签备注,然后一键跑完整流水线:PDF 拆页 → zip 解包 → OCR → 自动标签 → 年月派生 → 归组 → 发布。
AI 供应商选择与优先级链、模型名覆盖、自定义 OpenAI 兼容接口。
注意截图里每一家都显示**(无 key)——设置页永远不回显密钥**,只告诉你配没配。
浏览器菜单「添加到主屏幕」,独立图标、全屏运行。Service Worker 会缓存缩略图,二次浏览很快、弱网也顺。
这套东西一个仓库两半,可以只用一半,也可以两半一起用:
本地端(local/) |
在线端(仓库根) | |
|---|---|---|
| 跑在哪 | 你自己的电脑 | Cloudflare / Zeabur / 你的 VPS |
| 语言 | Python | JavaScript(同一份代码跑两个平台) |
| 建库(OCR) | ✅ 只有本地能做 | ❌ |
| 上传 / 入库 / 拍照矫正 | ✅ | ❌ |
| 检索 / 浏览 / 看图 | ✅ | ✅ |
| 改标签 / 备注 / 分组 / 日期 | ✅ | ✅ |
| AI 对话找图 | ✅ | ✅ |
| 证件 PDF / 发消息 | ✅ | ❌ |
| 手机随时随地打开 | 要自己开隧道 | ✅ 开箱即用 |
| 图片会离开你的机器吗 | 不会 | 看你选的存图方案(§D) |
| 花钱吗 | 电费 | 0 元起(Cloudflare 免费额度) |
- 只在家用、最在意隐私 → 只装本地端。功能最全,完全不上网。
- 想在外面用手机翻 → 两个都要:家里电脑建库,把库和图同步到在线端。
- 想先试试这个界面好不好用 → 只部署在线端。空库也能完整跑起来,五分钟。
本地部署
- 👍 功能最全(建库 / 拍照矫正 / 证件 PDF / 发消息只有本地有)
- 👍 图片和 OCR 全文一个字节都不出本机
- 👍 不花钱,不依赖任何第三方服务
- 👎 装 Python 环境要花点工夫(首次装依赖约 1 GB,含 OCR 引擎)
- 👎 电脑关机就访问不了;想在外面用得自己开内网穿透
- 👎 首轮建库慢(几千张图可能要跑几个小时)
在线部署
- 👍 五分钟上线,手机随时打开,HTTPS 自动
- 👍 不用管电脑开没开机
- 👍 Cloudflare 路线免费额度足够个人使用
- 👎 建不了库——OCR 流水线只在本地端,在线端只能读已有的库
- 👎 图和 OCR 全文要放到平台或网盘上,隐私面变大
- 👎 上传 / 拍照 / 证件 PDF 这些用不了
标注说明:不标 = 本地 / 在线都能用;【本地】 = 需要本地后端(文件系统 / 本机通道),在线端会自动隐藏入口或礼貌提示。
🔎 检索
- 多词 AND 检索,一次覆盖七路字段:文件名 / 别名 / OCR 全文 / 自动关键词 / 手动标签 / 分组名 / 备注
- 相关性排序:文件名·标签·分组命中 > 自动关键词 > 备注 > OCR 全文
- 命中片段上下文预览(snippet)
- 年月筛选、"图片类"(低文字图)筛选、组内筛选,可与关键词叠加
- 分页加载(每页 60,顺序稳定不跳动)
📅 按时间浏览
- 不输任何条件即是年月卡片总览:每月 2×2 封面缩略图 + 张数,点卡片进该月,「← 返回所有年月」一键回来
- 悬浮「时间」侧条随时切换年月
🏷️ 标签
- 自动关键词(本地流水线生成:文字图 jieba 抽词、低文字图视觉模型识主题)→ 标签云按词频展示、点击即搜
- 手动特征标签(多选):大图里直接编辑,或批量统一加;与自动词分区显示,可删可点搜
- 标签云 / 分组列表都带即时过滤框
📁 分组与类别
- 分组 = 一图一组(单组模型),本地物理对应
_groups\<组名>\文件夹 - 类别(分组的上一级):两级可折叠树、默认全展开、自动"未分类"节点
- 类别管理弹窗:新建 / 改名(重名自动合并)/ 解散;复选 + 长按选中;拖拽归类(选中多组整批拖)
- 类别排序两模式(记忆在浏览器):自定义——拖动类别标题排列,顺序服务端持久化;最近更新——按类别下成员组的最新入库时间降序,往组里加了新照片的类别自动上浮
- 组内浏览:改组名 ✎【本地】、上传到本组 📤【本地】、指派类别 🏷
- 分组 ⇄ 年月双向可达:有分组的图物理住在组文件夹里,但保留年月字段——按组、按时间都能找到同一张
🖼️ 看图
- 大图查看:上下张(按钮 / 左右滑 / 方向键,循环)、OCR 全文、日期(含来源标记)、标签、分组、备注
- 全屏模式:双击或 ⛶ 进入,滚轮 / 双指缩放 1–6 倍、拖动平移、双击复位、Esc 退出
- 原图调阅:若这张图关联了一张未裁剪的原图,大图里会出现「原图」按钮,随时调阅那张更完整的原始扫描
✏️ 编辑(在线端一样能改)
- 备注、手动标签、分组(组合框选现有或输入新建)、修正日期(YYYY-MM,单张或批量)
- 多选批量条:点选 / Shift 区间选,统一加标签 / 设分组 / 改日期 / 发送 / 证件 PDF / 删除(管理员)
- 双写编辑日志:每次修改 ① 即时写库 ② 追加一条带
content_hash+ 时间戳的可移植日志——本地、在线各写各的,合并时按"最后写入胜",文件改名、换机都不丢改动
🗑️ 删除 / 重裁 / 旋转 / 撤回【本地 · 管理员】
- 删除已入库图:数据库记录 + 物理文件一并移除;带内容哈希护栏防误删、全程留痕;多选批量条可一次删多张
- 重裁替换:对关联了原图的图,基于原图重新裁剪后替换当前图,保留原编号 / 日期 / 标签 / 分组 / 备注与原图关联,仅像素与 OCR 文字更新
- 旋转:把已入库图顺时针 90°。旋转是图片的物理改变,统一走入库通道落地——界面即时反馈"已排队",下次入库时在主盘物理旋转、重跑 OCR,其余元数据全部保留
- 撤回待入库:入库前撤销收件箱里某个待入库文件
- 对外受限实例上删除 / 重裁 / 撤回走延迟队列,由下次入库在主盘落地
📤 输出【本地】
- 发送到微信 / 飞书 / Telegram:单张、批量(≤9)、可选发送渠道精确到端
- 证件 PDF 拼版:20 种证件真实尺寸 + A4 整页,每图独立裁剪框、水印、每页张数、画质档(300 / 500 DPI),下载或直接发送
🤖 AI 对话找图
- 自然语言描述要找什么,AI 自主调用检索工具、多轮换词再试,用中文回答并附命中图
- 供应商:内置 DeepSeek / 阿里云 Qwen / MiniMax / Kimi + 自定义 OpenAI 兼容接口(最多 6 个);优先级链失败自动落到下一家;设置页一键测试连通
- 隐私边界:只发送候选图的 OCR 文字给 AI 平台,不上传图片本身;可用
AI_LIBRARY_HINT私下告诉 AI 你库里的内容类型,提升选词准确度 - 查某张图的处理历史:AI 只读调取该图的全生命时间线(入库 / 加原图 / 重裁 / 旋转 / 删除,按内容哈希归集、挪夹改名不丢)——只查不改,AI 永远碰不到文件
📷 拍照 / 扫描【本地】
- 上传页「📷 拍照 / 扫描」→ 手机拍照或选图 → 全屏四角编辑器:四个手柄拖动定位纸张四角,触屏可拖、带放大镜精准对齐
- 默认全选、可一键找角:默认四角=铺满整张图(拍得干净时直接下一步),可手动微调,或点「自动找边」让程序找纸张四角
- 旋转键:入库前当场顺时针 90°、即时重绘
- 四档增强预览,即点即换:
- 彩色去阴影——保留颜色、抹平打光不均
- 黑白自适应阈值——纯文字件最清晰、体积最小
- 文档白化——分通道除背景 + 抬白纸面 + 轻锐化,发黄 / 偏灰的文档变纯白,但保留印章、彩色、淡铅笔字,且明显瘦身
- 原图仅矫正——只做透视校正、不改像素
- 纯传统图像处理:透视矫正 + 适度锐化,绝不做 AI 超分或"补字"——证件、合同一个像素都不臆造
- 可保留原图:勾选后未裁剪的整张原图一并留档
- 确认即加入上传队列,走与普通上传完全相同的入库流水线
📥 上传与入库【本地】
- 网页上传暂存:拖放 / 多选,图片 / PDF / zip,选组或新建组、附标签备注,逐文件进度
- 待入库可见性:顶部统计显示「📥 待入库 N 件」,上传页列出收件箱里按组分列的待入库文件——入库前就知道传了什么
- 一键入库流水线:PDF 拆页 → zip 解包 → 子文件夹自动成组 → OCR → 自动标签 → 套用上传时的备注标签 → 多源年月派生 → 组物理归位 → 视觉打标 → 发布
- 重传对账:感知哈希(pHash)识别"同图不同字节"的重传,保留新版不产生重复
🧾 图转文 / 成绩单【本地 · 需伴随识别服务】
- 把图片(票据 / 表单 / 成绩单 / 书页…)识别成结构化文档(md / xlsx / docx):选图 → 选模式 → 一键跑 → 下载产物
- 由 ScanDex 后端反向代理到本机一个独立的识别服务(回环
127.0.0.1,绝不经公网暴露);只透传 5 条"干活"端点,白名单之外一律 404 - 复用 ScanDex 的登录 + 权限:挂在登录墙内侧,需
upload权限才显示——不单独开隧道、不额外放行 - 开关:环境变量
IMGTODOC_ENABLED(默认开;设0整体隐藏)
🔐 登录、用户与权限
- 多用户 + 权限系统:管理员在「用户管理」里建用户(无开放注册)、逐人开权限;新用户默认零权限,首次登录强制改密
- 权限项(依赖自动带上):查看/搜索 · 上传(→查看) · AI 对话(→查看) · 编辑信息(→查看) · 结构管理(→编辑) · 发送/导出(→查看) · 修改密码
- 管理员专属(不下放):改组名 · 类别管理 · 删除已入库图 · 撤回待入库 · 重裁 · 旋转 · 用户管理 · 设置 / 供应商测试 · 一键入库 · 审核队列
- 密码只存 PBKDF2 加盐哈希——任何人(含管理员)无法查看,管理员只能重置;重置后旧会话立即失效
- 权限在服务端逐端点强制(前端隐藏只是体验);用户管理与编辑操作全部留审计日志,署名到人
- 在线端 fail-closed(无任何账号整站 503)、Cookie
HttpOnly + SameSite=Strict(HTTPS 下加Secure)、会话带 TTL - 静态页面也在登录墙内,未登录连界面都拿不到
- 密钥只存平台 Secrets / 本地 gitignore 配置;设置页只显示"已配 / 未配",永不回显 key
⚙️ 设置
- 对话 / 视觉供应商选择、优先级顺序、模型名覆盖(留空用默认)、自定义 API
- 默认打开界面记忆(关键词 / 标签云 / 分组 / AI)
- 本地专属【本地】:一键入库按钮、重传对账开关
📱 装成 App(PWA)
- 添加到主屏幕:手机浏览器菜单里一点,即得独立图标、全屏运行(桌面 Chrome / Edge 地址栏也有安装入口)
- 缩略图离线缓存:Service Worker 只对库内缩略图做缓存优先(上限约 3000 张、FIFO 滚动);页面、
/api/*、待入库图一律不缓存——避免钉住旧登录态或过期数据 - 需 HTTPS 才启用:Service Worker 仅在安全上下文注册,本地裸 HTTP 自动跳过(不影响使用)
- 全部静态实现(
manifest.webmanifest+sw.js+icons/),部署即带
下载整个仓库(Code → Download ZIP 或 git clone),然后看 local/ —— 那里写了一份给 AI 读的部署指引:在解压出来的文件夹里打开 Claude Code / Cursor / 任何能读文件的 AI 助手,让它读 local/部署指引-给AI.md,它会问你几个问题(图片放在哪、要不要 AI 功能、密码设什么…)然后一步步装好。
别只拆
local\出来——本地后端要读仓库根的public\才有完整界面。
看 DEPLOY.md,三条路线任选:
| 路线 | 一句话 | 门槛 |
|---|---|---|
| Cloudflare Workers | 全云端、零服务器、免费额度够个人用 | 有 Cloudflare + GitHub 账号即可 |
| Zeabur | 认 Dockerfile 自动构建,挂个磁盘就完事 | 最省心 |
| VPS / Docker | docker run 一行,或直接 node server/node.mjs |
要有台机器 |
三条路线跑同一份代码,功能与接口完全一致。自托管零 npm 运行时依赖(D1 → node:sqlite、KV → 同一个 SQLite 文件、静态资源 → 读 public/,全是 Node 内置能力)。
npm install # 只装 wrangler(开发用)
npx wrangler dev # Cloudflare 本地模拟,配 .dev.vars 放测试密码(已 gitignore)
node server/node.mjs # 或者直接用 Node 跑自托管形态
node server/smoke.mjs # 冒烟自检:十几条断言,全绿才算适配层没坏前端就是 public/ 下两个自包含的 HTML,改文件刷新即见,没有构建步骤。
浏览器 ── 静态前端 (public/index.html,纯 HTML/CSS/JS 内联) ──┐
│ 调 /api/*
┌─────────────────────────────────────┴────────┐
本地: │ serve.py (Python,在 local/ 目录) │ ← 本机日常,功能最全
在线: │ worker/index.js │
│ ├─ Cloudflare Workers(原生) │
│ └─ server/node.mjs(自托管:Zeabur/VPS/本机)│
└───────┬──────────────┬───────────────┬───────┘
库(D1 或 设置/会话/类别 图片
SQLite 文件) (KV 或同一文件) (WebDAV 或磁盘)
三条要点:
- 前端单一源:本地后端与在线后端读的是同一份
public/,改一处两端生效 - 后端单一源:
worker/index.js是唯一的在线实现,Cloudflare 与自托管只是绑定不同(server/shims/里三个替身),业务逻辑一行不重写 - API 契约一致:在线端按本地
serve.py的接口逐句对齐,前端无感切换;空库时读接口返回空结构,界面完整可用
读:GET /api/facets(年月+封面+组数) · search(q/ym/low/group/off 分页) · tags · groups · supergroups(类别字典·键序=自定义顺序 + updated=每类别成员组最新入库时间) · inbox_pending · config · settings · channels · thumb?id= · image?id= · original?hash= · zip_list/zip_thumb/zip_image · cert_types
写(POST JSON):login/logout · change_password · 用户管理(管理员) users/user_create/user_delete/user_perms/user_reset/user_rename · note · manual_tags · set_date(单/批) · groups_set · group_add(单组=替换) · group_rename【本地】 · supergroup_assign/rename/delete · supergroup_order · tag_add · upload【本地】 · scan_process/scan_detect/scan_ai【本地】 · image_delete/inbox_cancel/image_recrop/image_rotate【本地·管理员】 · ingest_inbox【本地】 · send/zip_send【本地】 · chat · cert_pdf【本地】 · test_provider
图转文代理【本地】:GET /api/imgtodoc/status·progress·download + POST /api/imgtodoc/upload·run → 反代本机识别服务(白名单 5 条,其余 404);同受登录墙 + upload 权限约束。
模式开关(/api/config 返回):
local_inbox— 有本地收件箱(可上传 / 入库)public— 对外受限实例:服务端封禁入库 / 设置写 / 供应商测试 / 关机;编辑类全部可用(备注 / 标签 / 分组 / 改日期 / 类别管理即时生效,改组名走延迟落地)cloud— 在线端imgtodoc— 图转文模块是否启用
数据模型:图片 → 分组 = 一图一组;分组 → 类别 = 一组一类别(纯显示层);图片 → 标签 = 多选;年月来源优先级 手动 > 原件 > EXIF > 文件名 > 派生 > mtime。
scandex/
├─ public/ 静态前端 = 平台的资产目录
│ ├─ index.html 主界面(自包含,无构建步骤)
│ ├─ login.html 登录页
│ ├─ sw.js Service Worker(只缓存缩略图)
│ ├─ manifest.webmanifest
│ └─ icons/ PWA 图标 + 安装预览图(未登录可访问的最小集合之一)
├─ worker/
│ ├─ index.js ★ 唯一的在线后端实现:登录墙 / 检索 / 类别 / 编辑双写 / AI 对话 / 图片代理
│ └─ schema.sql 初始 schema(空库即可跑;含 edits 编辑日志表)
├─ server/ 自托管适配层(让同一份 worker 跑在 Node 上)
│ ├─ node.mjs HTTP 入口:node:http ⇄ Web Request/Response
│ ├─ shims/d1.mjs D1 → node:sqlite
│ ├─ shims/kv.mjs KV → 同一个 SQLite 文件里的 kv 表
│ ├─ shims/assets.mjs ASSETS → 读 public/
│ ├─ shims/localfiles.mjs 本地磁盘图源(IMAGE_ROOT)
│ └─ smoke.mjs 冒烟自检
├─ Dockerfile Zeabur / 任意 Docker 主机(node:24-alpine,零 npm 依赖)
├─ wrangler.toml Cloudflare 绑定(d1/kv id 按 DEPLOY.md 填;零密钥)
├─ docs/img/ README 截图(全部为合成演示数据)
├─ local/ ★ 本地端(Python):离线 OCR 流水线 + 全功能后端
│ ├─ scripts/ 流水线:ocr_index / generate_tags / organize_files / update …
│ │ └─ _paths.py 全部路径的唯一真源(环境变量 / config.local.json 驱动)
│ ├─ webui/serve.py 本地后端(读上一级的 public/ 当界面)
│ ├─ client/ 只读数据接口客户端
│ ├─ requirements.txt
│ ├─ README.md 本地端说明
│ └─ 部署指引-给AI.md ★ 交给 AI 读的安装指引
├─ DEPLOY.md ★ 部署手册:三条路线 + 环境变量总表 + 存图方案 + 验证清单
├─ README.md 本文件
└─ README.en.md English
这个项目处理的是你家全部证件、票据、合同的原件影像和全文,所以隐私不是加分项而是前提:
- OCR 全程离线:识别在你自己的机器上跑(RapidOCR / ONNX Runtime),图片不发给任何人
- AI 只拿文字:用 AI 对话找图时,发出去的是候选图的 OCR 文字,图片本身不上传
- 仓库零密钥:所有 key 存平台 Secrets 或本地 gitignore 的配置文件;设置页只显示"已配 / 未配"
- 登录墙在静态页之前:未登录连界面都拿不到。未登录可访问的只有登录页、
manifest.webmanifest、sw.js、icons/icon-*.png——PWA 安装必需且零敏感 - fail-closed:没配管理员就整站 503,不存在"忘了设密码裸奔"的窗口
- 密码只存哈希:PBKDF2 加盐,常数时间校验,任何人看不到明文
详细的威胁模型与加固建议见 DEPLOY.md §I。
一句实在话:最安全的用法是只在本地跑。要上公网,请用密码管理器生成的长随机密码,并认真读一遍 §D 图片存哪——那一节决定了你的原件影像会落在谁的硬盘上。
MIT。可自由使用、修改、分发、商用,保留版权声明即可,不提供任何担保。










