From 5899e447918442ac7931bf4abd000ecb485af655 Mon Sep 17 00:00:00 2001 From: Saberlve Date: Wed, 15 Apr 2026 16:56:59 +0800 Subject: [PATCH] docs: add two-step import+card generation design spec Co-Authored-By: Claude Opus 4.6 --- .../2026-04-15-import-card-two-step-design.md | 180 ++++++++++++++++++ 1 file changed, 180 insertions(+) create mode 100644 docs/superpowers/specs/2026-04-15-import-card-two-step-design.md diff --git a/docs/superpowers/specs/2026-04-15-import-card-two-step-design.md b/docs/superpowers/specs/2026-04-15-import-card-two-step-design.md new file mode 100644 index 0000000..c01f7c7 --- /dev/null +++ b/docs/superpowers/specs/2026-04-15-import-card-two-step-design.md @@ -0,0 +1,180 @@ +# Import + Card Generation Two-Step Design + +## Status + +Draft — not yet approved. + +## Overview + +Split the current "import文献 + immediately generate cards" flow into two distinct steps: + +1. **Import** — items are registered to a project, but cards are NOT generated yet. The system checks a global registry for existing cards. +2. **Manual batch generate** — user manually selects pending items and triggers card generation. Same item across different projects reuses existing cards (hash-based deduplication). + +## Data Model + +### Global Registry (existing, unchanged) + +- `library/index/items.json` — global item metadata registry, keyed by `item_key` +- `library/index/cards.json` — global card index, keyed by `item_key` + +### Project Registry (existing, extended) + +- `projects/{project_id}/selected-items.json` — list of `item_key`s associated with the project +- `projects/{project_id}/pending-items.json` — **removed** — pending status is computed dynamically as `selected_items` minus keys present in `cards.json`. No separate tracking file needed. + +### Import State + +For each item in a project, one of two states: + +| State | Meaning | +|-------|---------| +| `pending` | item is imported but no card exists in `cards.json` | +| `done` | card exists in `cards.json` for this item_key | + +## API Endpoints + +### `GET /api/projects/{project_id}/import-state` + +Returns the import state of all items in a project. + +**Response:** + +```json +{ + "project_id": "proj-123", + "items": [ + { + "item_key": "ABCD1234", + "title": "Paper Title", + "creators": ["Author A", "Author B"], + "year": 2024, + "item_type": "journalArticle", + "card_status": "done" + }, + { + "item_key": "EFGH5678", + "title": "Another Paper", + "creators": ["Author C"], + "year": 2023, + "item_type": "conferencePaper", + "card_status": "pending" + } + ], + "pending_count": 1, + "done_count": 1 +} +``` + +**Logic:** Iterate `selected-items.json`, for each `item_key` check if it exists in `cards.json`. If yes → `done`, else → `pending`. + +### `POST /api/projects/{project_id}/cards/generate` + +Receives a list of item_keys to generate cards for. Generates cards one by one, updates `cards.json`. + +**Request:** + +```json +{ + "item_keys": ["EFGH5678", "IJKL9012"] +} +``` + +**Response (streaming or incremental):** + +```json +{ + "project_id": "proj-123", + "generated": ["EFGH5678"], + "failed": [], + "items": [ + { "item_key": "EFGH5678", "title": "...", "creators": [...], "card_status": "done" } + ] +} +``` + +**Logic:** + +1. For each `item_key` in `item_keys`: + - Read item metadata from `items.json` (or from Zotero reader if not yet in items.json) + - Call `CardBuilder.build_or_update()` which computes `source_hash` and either creates or updates the card + - Send incremental progress event (SSE or via callback) +2. After all complete, return the updated item states. + +### `POST /api/projects/{project_id}/imports/item-keys` (existing, extended) + +When items are imported, they are added to `selected-items.json`. Cards are NOT generated immediately. + +## Frontend: Import Window Layout + +### Two-Column Layout + +``` +┌─────────────────────────────────────────────────────────┐ +│ [title bar: 导入文献] [_] [□] [×] │ +├───────────────────────┬─────────────────────────────────┤ +│ LEFT (40%) │ RIGHT (60%) │ +│ ┌─────────────────┐ │ ┌───────────────────────────┐ │ +│ │ Search... │ │ │ Title: ... │ │ +│ └─────────────────┘ │ │ Authors: ... │ │ +│ ┌─────────────────┐ │ │ Year: ... │ │ +│ │ □ Paper A [✓] │ │ │ Type: journalArticle │ │ +│ │ ☑ Paper B │ │ │ Abstract: ... │ │ +│ │ ✓ Paper C │ │ │ Tags: tag1, tag2 │ │ +│ │ □ Paper D │ │ └───────────────────────────┘ │ +│ └─────────────────┘ │ │ +│ Pending (2) Done (1) │ │ +├───────────────────────┴─────────────────────────────────┤ +│ [生成选中卡片 (2)] │ +└─────────────────────────────────────────────────────────┘ +``` + +### Visual States + +- **pending items** — light background, checkbox visible, unchecked by default +- **done items** — green tint / checkmark, no checkbox, not selectable for generation +- **selected pending** — highlighted border, checkbox checked +- **generating** — spinner icon, row slightly dimmed + +### Interactions + +1. Click item in left list → right panel shows metadata preview +2. Click checkbox on pending item → toggles selection +3. "Select All Pending" link at top of list +4. Click "生成选中卡片 (N)" button → triggers `POST .../cards/generate` +5. During generation: button becomes progress bar "生成 X/Y",completed items turn done in real time + +### Card Status Legend + +Shown at bottom of left panel: + +``` +Pending (N) ● Done (N) ✓ +``` + +## Deduplication Logic + +`CardBuilder.build_or_update()` computes `source_hash` from: + +``` +{ + item_key, title, creators, year, item_type, + abstract, tags, collection_paths, notes, attachment_texts +} +``` + +- If `cards.json` already has an entry for this `item_key` with the same `source_hash` → skip LLM call, card already up to date +- If hash differs → regenerate (overwrite) +- This means the same `item_key` imported into multiple projects will reuse the same card if the source hasn't changed + +## Error Handling + +- If a single item fails to generate (LLM error, missing data), mark it as failed and continue with others +- Failed items shown with error icon, can be retried individually or as batch +- Return `failed` array in response with error reasons + +## Out of Scope + +- Streaming/SSE for real-time progress (use simple polling or single request for now) +- Background job queue +- Version history for regenerated cards