docs: add two-step import+card generation design spec

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Saberlve 2026-04-15 16:56:59 +08:00
parent b955d382e3
commit 5899e44791

View File

@ -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