181 lines
6.6 KiB
Markdown
181 lines
6.6 KiB
Markdown
# 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
|