# grocy_plus_plus **Repository Path**: qyj1412/grocy_plus_plus ## Basic Information - **Project Name**: grocy_plus_plus - **Description**: No description available - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: main - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-07-19 - **Last Updated**: 2026-08-10 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # Grocy++ 拍照识别 + 保质期管理的智能食材库存系统。自带本地 SQLite 数据库,不再依赖 外部服务,全部数据存储在本地 SQLite 数据库中。 ## What this is | Layer | Tech | Notes | |-------|------|-------| | Frontend | Plain HTML + CSS + vanilla JS | iOS 风格 UI,零构建 | | Backend | Python 3.12 + FastAPI | 单进程,~150 MB 镜像 | | Vision | Pluggable provider | Default: OpenCodeGo; 集成 Qwen, MiniMax, Xiaomi, GLM, Kimi, DeepSeek | | Storage | Local SQLite (`data/grocypp.db`) | 自动建表,零配置 | ## Quick start (local) ```bash # 1. clone & install cd backend python -m venv .venv .venv\Scripts\activate # Windows # or: source .venv/bin/activate # Linux/Mac pip install -r requirements.txt # 2. configure cp .env.example .env # edit .env — fill in at least one vision API key (the OPENCODEGO_* block # is the default; you can also uncomment any other provider) # 3. run uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload ``` Then open `http://localhost:8000/`. The three UI pages (`index.html`, `scan-import.html`, `item-management.html`) are served from the root URL. ## Quick start (Docker) ```bash cp backend/.env.example backend/.env # edit backend/.env docker compose up --build ``` The image: - is a multi-stage build (`python:3.12-slim` → ~150 MB final) - runs as an unprivileged user - starts under `tini` so signals are forwarded properly - SQLite 数据库自动创建在 Docker volume `grocypp_data:/app/data` 中 ## Configuration Everything is env-var driven. See `backend/.env.example` for the full list. The most important knobs: | Var | Default | Purpose | |-----|---------|---------| | `PORT` | `8000` | 监听端口 | | `DB_PATH` | `data/grocypp.db` | SQLite 数据库文件路径 | | `VISION_PROVIDER` | `opencodego` | 视觉识别提供商:`opencodego`, `qwen`, `MiniMax`, `xiaomi`, `glm`, `kimi`, `deepseek` | | `_API_KEY` | _empty_ | 对应提供商的 API 密钥 | | `VISION_MAX_IMAGE_SIDE` | `1280` | 上传图片最长边缩放至该像素数 | ### Switching vision providers Just set `VISION_PROVIDER=...` and fill in the matching key. All providers speak the same OpenAI-compatible chat-completions API, so swapping is a one-line change. | Provider | Notes | |----------|-------| | `opencodego` | Default. Unified gateway, OpenAI-compatible. | | `qwen` | Alibaba DashScope. Default model `qwen-vl-plus`. | | `MiniMax` | OpenAI-compatible endpoint. | | `xiaomi` | MiMo multimodal. | | `glm` | Zhipu BigModel. Default model `glm-4v-plus`. | | `kimi` | Moonshot. | | `deepseek` | **No image support yet** — fails with a clear error so you can fall back. | ## How the photo flow works 1. User taps the **扫描导入** card → camera modal opens. 2. `getUserMedia({ video: { facingMode: 'environment' } })` shows the back camera. (Desktop / no camera → falls back to a file picker via ``. **HTTPS is required for the camera on iOS Safari** — `http://localhost` is treated as a secure context on most browsers.) 3. Capture button grabs a frame to `` → `toBlob('image/jpeg', 0.9)` → `URL.createObjectURL` for the thumbnail. 4. Up to 3 photos can be staged. Hitting **开始上传** sends them all in a single POST to `/api/scan/upload-batch` as `multipart/form-data` with real `xhr.upload.onprogress` for the bar. 5. The backend: 1. calls the configured vision provider, 2. parses out `{name, category, expiry_date, production_date, confidence}`, 3. if `expiry_date` is `null`, falls back to the heuristic table in `app/data/items.json` (seasonal + storage-adjusted), 4. **finds or creates the product in the local SQLite database**, 5. **adds a stock batch entry** tied to that product. 6. The queue shows success/failure with the recognised name and expiry date. Photos are normalized to JPEG and stored in the SQLite `stock.photo_data` column. Existing `uploads/` photos are copied into SQLite automatically on startup, so photo data survives container recreation alongside the rest of the database. ## Project layout ``` grocy++/ ├── ui/ # HTML/CSS/JS frontend, served at / │ ├── assets/common.css │ ├── index.html │ ├── scan-import.html │ └── item-management.html ├── backend/ │ ├── app/ │ │ ├── api/ # FastAPI routers │ │ │ ├── system.py # /api/system/{info,providers,healthz} │ │ │ ├── scan.py # /api/scan/{recognize,upload,upload-batch} │ │ │ ├── inventory.py # /api/inventory[/{id}/consume] │ │ │ └── products.py # /api/products[/{id}] │ │ ├── services/ │ │ │ ├── db_service.py # SQLite database access layer │ │ │ ├── heuristic.py # shelf-life fallback table │ │ │ └── vision/ # pluggable vision providers │ │ │ ├── base.py │ │ │ ├── opencodego.py │ │ │ ├── qwen.py │ │ │ ├── MiniMax.py │ │ │ ├── xiaomi.py │ │ │ ├── glm.py │ │ │ ├── kimi.py │ │ │ ├── deepseek.py # text-only stub │ │ │ └── registry.py │ │ ├── data/items.json # shelf-life reference table │ │ ├── database.py # SQLite schema & connection manager │ │ ├── config.py # pydantic-settings │ │ ├── deps.py # DI for DB / vision singletons │ │ └── main.py # FastAPI app factory │ ├── requirements.txt │ └── .env.example ├── Dockerfile # multi-stage, ~150 MB ├── docker-compose.yml ├── .dockerignore └── README.md ``` ## Database schema | Table | Purpose | |-------|---------| | `products` | 产品目录 (name, default_best_before_days, product_group) | | `stock` | 库存批次,每次扫描添加生成一行 (amount, best_before_date) | | `stock_log` | 入库/消耗/损坏的操作日志 | | `locations` | 存放位置(默认 / 冰箱 / 冷冻室) | | `shopping_list` | 购物清单 | | `barcodes` | 条形码 ↔ 产品映射(预留) | 数据库文件默认在 `backend/data/grocypp.db`,首次启动时自动建表。 ## API surface (auto-generated OpenAPI at `/docs`) | Method | Path | Purpose | |--------|------|---------| | `GET` | `/api/system/info` | 系统信息 + 数据库统计 | | `GET` | `/api/system/providers` | 列出已安装的视觉提供商 | | `POST` | `/api/scan/recognize` | 上传照片 → 识别结果(不写入数据库) | | `POST` | `/api/scan/upload` | 识别 + 写入库存(单张) | | `POST` | `/api/scan/upload-batch` | 识别 + 写入库存(多张,一次视觉调用) | | `GET` | `/api/inventory` | 当前库存,按到期日升序 | | `GET` | `/api/inventory/{stock_id}/photo` | 读取库存图片(SQLite 存储,含旧文件回填) | | `POST` | `/api/inventory/{product_id}/consume` | 消耗/损坏指定产品 | | `GET` | `/api/products` | 搜索产品目录 | | `POST` | `/api/products` | 创建新产品 | | `GET` | `/api/products/{id}` | 获取单个产品 | | `GET` | `/healthz` | 存活检测 | ## Extending - **New vision provider?** Subclass `OpenAICompatibleProvider` (or `VisionProvider` if it doesn't speak OpenAI chat-completions) and register it in `app/services/vision/registry.py:PROVIDERS`. Add the matching env-var fields in `app/config.py` and `backend/.env.example`. That's it. - **New heuristic shelf-life entry?** Append a row to `app/data/items.json` — keywords are matched longest-first against the recognised name, then a `display_name`, `category`, `shelf_life_days`, and an optional `rationale` is stored. - **New API route?** Drop a router file in `app/api/`, register it in `app/main.py:create_app`. DI for the DB / vision clients is in `app/deps.py`. ## License MIT for the wrapper code; the `ui/` folder is the output of Open-Design and inherits whatever license came with that export.