Andrej Janchevski commited on
Commit
f31c85c
Β·
1 Parent(s): 6cc7c3d

docs(plan): add frontend COINs demo plan

Browse files

Plan for the /demos/coins interactive page: query-structure picker,
dataset and algorithm selectors, SVG query-graph with per-node and
per-edge searchable dropdown pills that grow to fit entity/relation
names and reflow the graph to avoid overlap, and a results dashboard
covering top-K predictions, community-rank callout, and step-by-step
timing vs baseline. Exercises all seven COINs backend endpoints with
every documented input/output field.

Files changed (1) hide show
  1. .claude/plans/frontend_coins_demo.md +194 -0
.claude/plans/frontend_coins_demo.md ADDED
@@ -0,0 +1,194 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Frontend COINs Demo: Interactive Query Builder + Reasoning Dashboard
2
+
3
+ Interactive Vue page at `/demos/coins` that lets the user pick a dataset, query structure, and embedding algorithm, fill anchors/relations/variables into a visual query template via searchable dropdowns, click "Reason", and view a dashboard with ranked entity predictions plus COINs-specific stats (community rank, step timing, speedup vs baseline). Exercises **every** COINs backend endpoint and every documented input/output field.
4
+
5
+ ## Context
6
+
7
+ The backend exposes seven COINs endpoints under `/api/v1/coins/` (`src/backend/api/views/coins.py`, fully spec'd in `docs/api.yaml`): `GET /datasets`, `GET /datasets/{id}/entities`, `GET /datasets/{id}/relations`, `GET /datasets/{id}/sample-triples`, `GET /models`, `GET /query-structures`, `POST /predict`. The frontend (`src/frontend/`, Vue 3 + Vite + Fomantic-UI + axios + Pinia) currently only ships Home and CV. The Home page has a "Coming soon" `DemoPreviewCard` for COINs and already uses `sampleTriples('nell', ...)` in `FactOfTheDay.vue`, plus the SVG motif-rendering pattern in `components/background/FloatingMotif.vue` β€” both are the visual/technical reference for this demo. Plan `.claude/plans/backend_coins.md` describes the inference semantics (community rank `rank_c`, `c_err`, Prop. 3.1 speedup) that the dashboard surfaces to the user. This demo is the first of three research demos; `/demos/<slug>` namespacing leaves room for MultiProxAn and KG-Anomaly.
8
+
9
+ ## Assumptions and Constraints
10
+
11
+ - Stack stays Vue 3 + Vite + vue-router 4 + Pinia + axios + Fomantic-UI CSS. **No new heavy deps** β€” the query-structure visualization is hand-rolled SVG (query templates have ≀5 nodes / ≀3 edges, mirroring the `FloatingMotif.vue` style).
12
+ - All calls go through `src/frontend/src/api/client.js` (`/api/v1` base URL, `apiError()` for messages). All COINs calls live in `src/frontend/src/api/coins.js` (extending the two existing functions).
13
+ - The backend enforces a **global inference lock** (`INFERENCE_BUSY` β†’ HTTP 429). The UI must serialize `POST /coins/predict` per tab and surface 429 with a friendly retry hint.
14
+ - Entity/relation lists are **large** (NELL ~66 k entities, Freebase ~14 k, WordNet ~41 k). Searchable dropdowns must use server-side search via `?q=&page=&page_size=` with debounced typing (β‰₯200 ms) β€” never load the full list client-side.
15
+ - Algorithm support varies by structure (only `q2b` supports `2p/3p/2i/3i/ip/pi`; `transe/distmult/complex/rotate/kbgat` only `1p`) and by dataset (`models.available_datasets`). The UI filters selectable algorithms by both.
16
+ - Green palette and dark-mode CSS custom properties from `styles/tokens.css` drive all new styles. Respect `prefers-reduced-motion`. Responsive from 320 px up.
17
+ - **Docs hygiene (per CLAUDE.md):** this plan does not change backend endpoints, so no backend-docs updates needed. If inference shape changes later, update `docs/api.yaml`, `docs/postman/`, and `src/backend/README.md` in the same commit.
18
+
19
+ ## Scope
20
+
21
+ **In scope**
22
+ - New route `/demos/coins` with one view and one Pinia store.
23
+ - Query-builder UI: dataset selector, query-structure selector (cards), algorithm selector (filtered), hand-rolled SVG query-graph visualization with entity/relation dropdowns positioned over nodes/edges, top-K slider, "Reason" button.
24
+ - Reusable `SearchableEntityDropdown` / `SearchableRelationDropdown` components (server-side paginated search with debounce).
25
+ - Reusable `QueryStructurePicker` cards that render each template via the shared SVG renderer.
26
+ - Results dashboard: human-readable `query_description`, top-K predictions list (rank, intra-community rank, entity name, score), **Community rank callout** explaining `rank_c`, **Timing panel** with step1/step2/total bars and speedup multiplier + baseline estimate, loading + 429/error states.
27
+ - API wrappers for the five new COINs endpoints; update the existing `sampleTriples` and add `getDatasets`.
28
+ - Nav: add "Demos" link in `NavBar.vue` pointing to `/demos/coins` (single entry now; becomes a dropdown when other demos ship).
29
+ - Home page wiring: update the COINs `DemoPreviewCard` to link to `/demos/coins` and drop the "Coming soon" label.
30
+ - Manual browser verification against a locally running backend (GPU box available per user memory).
31
+
32
+ **Out of scope**
33
+ - MultiProxAn and KG-Anomaly demos.
34
+ - Unit/e2e tests (manual browser verification only, matching the existing frontend-plan precedent).
35
+ - Persisting query state across reloads / shareable URLs (nice-to-have for a later iteration).
36
+ - Advanced KG subgraph visualization around the predicted entity (target node only; no neighbour expansion).
37
+
38
+ ## Design
39
+
40
+ ### New directory layout
41
+
42
+ ```
43
+ src/frontend/src/
44
+ β”œβ”€β”€ api/
45
+ β”‚ └── coins.js # extended: datasets, entities, relations, sampleTriples, models, queryStructures, predict
46
+ β”œβ”€β”€ components/
47
+ β”‚ └── coins/
48
+ β”‚ β”œβ”€β”€ QueryStructurePicker.vue # grid of cards, each renders template via QueryGraph
49
+ β”‚ β”œβ”€β”€ QueryGraph.vue # shared SVG renderer: nodes + edges from a structure spec, with <slot> overlays per node/edge for dropdowns/labels
50
+ β”‚ β”œβ”€β”€ SearchableEntityDropdown.vue # debounced server-side entity search; emits {id, name, label}
51
+ β”‚ β”œβ”€β”€ SearchableRelationDropdown.vue # debounced server-side relation search
52
+ β”‚ β”œβ”€β”€ AlgorithmSelector.vue # filters registry by query_structure + dataset
53
+ β”‚ β”œβ”€β”€ ResultsDashboard.vue # composes the three panels below
54
+ β”‚ β”œβ”€β”€ QueryDescription.vue # renders response.query_description as a labelled chain
55
+ β”‚ β”œβ”€β”€ PredictionList.vue # ranked top-K with rank/intra_community_rank/score
56
+ β”‚ β”œβ”€β”€ CommunityRankCallout.vue # rank_c explainer card
57
+ β”‚ └── TimingPanel.vue # step1/step2/total bars, speedup multiplier, baseline
58
+ β”œβ”€β”€ stores/
59
+ β”‚ └── coinsDemo.js # Pinia: dataset, queryStructure, algorithm, anchors, variables, relations, topK, result, loading, error
60
+ β”œβ”€β”€ views/
61
+ β”‚ └── demos/
62
+ β”‚ └── CoinsView.vue # composes selector row + QueryGraph (in edit mode) + Reason button + ResultsDashboard
63
+ └── router/
64
+ └── index.js # adds `/demos/coins` route
65
+ ```
66
+
67
+ ### Data flow
68
+
69
+ 1. **On mount** of `CoinsView.vue`, fire in parallel:
70
+ - `GET /coins/datasets` β†’ populates dataset selector (shows `name`, `num_entities`, `num_relations`, `description` tooltip).
71
+ - `GET /coins/models` β†’ registry of `{algorithm, name, description, supported_query_structures, available_datasets}`.
72
+ - `GET /coins/query-structures` β†’ array of `{id, name, description, nodes[], edges[]}` consumed by both `QueryStructurePicker` and `QueryGraph`.
73
+ 2. User picks **dataset** β†’ store commits it. Anchors/variables cleared (entity IDs are dataset-scoped).
74
+ 3. User picks **query structure** (`QueryStructurePicker` cards, each a mini `QueryGraph` render). Store resets `anchors`, `variables`, `relations` to the exact node/edge IDs defined in the chosen structure.
75
+ 4. `AlgorithmSelector` recomputes the allowed list: `models.filter(m => m.supported_query_structures.includes(currentStructure) && m.available_datasets.includes(currentDataset))`. Auto-selects the first (typically `q2b` for multi-hop, `rotate` for `1p`).
76
+ 5. Main edit area renders `QueryGraph` with the chosen structure. Over each **anchor node** it slots a `SearchableEntityDropdown` (required). Over each **variable node** it slots a `SearchableEntityDropdown` with a "Let model pick" toggle that deletes the key from `variables` when off. Over each **edge** it slots a `SearchableRelationDropdown` (required). Target node is rendered as a distinct "?" badge (never editable).
77
+ 6. Dropdown search β†’ `GET /coins/datasets/{id}/entities?q=&page=1&page_size=50` (or `/relations`). Debounced 250 ms, pagination via "Load more". Request cancels if input changes (axios `AbortController`).
78
+ 7. Under the graph: a `FactOfTheDay`-style **"Prefill with a random triple"** link β†’ `GET /coins/datasets/{id}/sample-triples?count=1&seed=<ISO date>` (reusing the existing seeded-daily convention), which fills `a`/`r1` for a `1p` query (skipped when structure β‰  `1p`). Uses the existing `sampleTriples` function.
79
+ 8. Top-K slider 1–10, defaults 10. "Reason" button disabled until all required anchor/relation slots are filled. Click β†’ `POST /coins/predict` with the **exact** payload:
80
+ ```json
81
+ {
82
+ "dataset_id": "...",
83
+ "algorithm": "...",
84
+ "query_structure": "...",
85
+ "anchors": { "a|a1|a2|a3": entity_id, ... },
86
+ "variables": { "v1|v2": entity_id, ... },
87
+ "relations": { "r1|r2|r3": relation_id, ... },
88
+ "top_k": 10
89
+ }
90
+ ```
91
+ `variables` is only included for pinned entries.
92
+ 9. Response drives `ResultsDashboard`:
93
+ - **`QueryDescription.vue`** renders `response.query_description`.
94
+ - **`PredictionList.vue`** lists each prediction with `rank`, `intra_community_rank`, `entity_name`, `score` (as a progress bar 0–1), linked to a `GET /coins/datasets/{id}/entities?q=<name>` detail peek.
95
+ - **`CommunityRankCallout.vue`** surfaces `timing.rank_c` with copy "Found in community #N out of the partitioned KG". If `rank_c == 0` the panel says "No valid KG answers" and the inference-error path is hit instead.
96
+ - **`TimingPanel.vue`** renders two horizontal bars (`step1_ms`, `step2_ms` with `step1_label`/`step2_label` captions), a third bar for `baseline_estimate_ms` (with `baseline_label`), and a headline "Γ—{speedup.toFixed(1)} faster than full-graph inference (Prop. 3.1)". `total_ms` shown as the sum.
97
+ 10. Error handling: `INFERENCE_BUSY` (429) β†’ yellow banner "Another query is running β€” retry in a moment"; `INFERENCE_ERROR` (422) β†’ red banner with `error.message` (typical case: no valid KG answers); `MODEL_UNAVAILABLE` (503) β†’ banner suggesting a different algorithm. All via the existing `apiError()` helper.
98
+
99
+ ### API wrappers (`src/frontend/src/api/coins.js`)
100
+
101
+ Extend the file to export:
102
+ - `listDatasets()` β†’ `GET /coins/datasets`
103
+ - `searchEntities(datasetId, { q, page, pageSize, signal })` β†’ `GET /coins/datasets/{id}/entities`
104
+ - `searchRelations(datasetId, { q, page, pageSize, signal })` β†’ `GET /coins/datasets/{id}/relations`
105
+ - `sampleTriples(datasetId, count, seed)` β€” existing, untouched
106
+ - `listModels()` β†’ `GET /coins/models`
107
+ - `listQueryStructures()` β†’ `GET /coins/query-structures`
108
+ - `predict(payload, { signal })` β†’ `POST /coins/predict`
109
+
110
+ All pass `signal` through to axios for cancellation. All return `response.data` unchanged.
111
+
112
+ ### `QueryGraph.vue` layout strategy
113
+
114
+ Use the `nodes[]` and `edges[]` from `GET /coins/query-structures` directly. For each of the 7 structures, assign fixed 2D **direction unit vectors** (not fixed pixel coordinates) keyed by `node.id` β€” the component computes a geometric skeleton in `viewBox` units, but the dropdown pills are the source of truth for pixel positions. Render:
115
+ - `<line>` per edge, arrow marker reused from `FloatingMotif.vue`'s `motif-arrow` pattern. Edge endpoints are recomputed each layout pass from the actual bounding boxes of the node pills (see below).
116
+ - A `<foreignObject>` per node and per edge exposing `<slot name="node-{id}" :node="node">` / `<slot name="edge-{id}" :edge="edge">`. The slot contents β€” the dropdown pills β€” are plain HTML and size themselves to their label text. Falls back to a small `<circle>` + text label when no slot is provided, so `QueryStructurePicker` cards render clean compact mini-previews.
117
+
118
+ ### Responsive pill sizing and layout
119
+
120
+ Entity and relation names vary by orders of magnitude (WordNet: "domestic_dog_NN_1"; Freebase: long multi-word labels; NELL: "concept_athletecoach_thomaspace"). The pills **must grow with their label** and the graph **must reflow** so pills never overlap.
121
+
122
+ - Each `SearchableEntityDropdown` / `SearchableRelationDropdown` renders as an **inline-block pill** (`.ui.label`-derived) with `width: max-content; max-width: min(40ch, 90vw); white-space: normal; overflow-wrap: anywhere; padding: 0.4em 0.8em`. Empty state shows a placeholder ("Pick entity", "Pick relation") so the pill has a sensible minimum size. Filled state shows `label || name`, truncated with a tooltip (`title=name`) only when it exceeds `max-width`.
123
+ - `QueryGraph.vue` uses a **two-pass layout**:
124
+ 1. Initial pass places each node at its direction unit-vector Γ— a base spacing (e.g. 160 px) inside the `viewBox`; edges are routed straight.
125
+ 2. After the DOM paints, a `ResizeObserver` attached to every pill measures actual bounding boxes; the component then expands the skeleton along each edge until no two pill boxes overlap (padded by 16 px), recomputes the `viewBox` to fit, and reroutes edge endpoints to the pill box edges (intersect line with rectangle) instead of a circle. The container's `height` follows the final `viewBox` height.
126
+ - The `ResizeObserver` fires on: initial mount, each dropdown selection change, window resize, and font-load (prevents FOUT-triggered overlap). Throttle via `requestAnimationFrame` to one reflow per frame.
127
+ - Below 600 px width, the SVG viewBox falls back to a vertical stack of pills connected by short down-arrows (a "flow" mode) instead of the 2D layout β€” this keeps long labels legible on phones without horizontal scrolling. The `FloatingMotif` arrow marker is reused unchanged.
128
+ - `QueryStructurePicker` cards render `QueryGraph` in **preview mode** with short static labels ("Anchor", "?"), bypassing the two-pass reflow (fixed coord map) so cards stay compact and uniform.
129
+
130
+ ### `stores/coinsDemo.js` shape
131
+
132
+ ```js
133
+ state: {
134
+ datasets: [], models: [], structures: [],
135
+ datasetId: '', queryStructure: '', algorithm: '',
136
+ anchors: {}, variables: {}, relations: {},
137
+ topK: 10,
138
+ loading: false, error: '',
139
+ result: null
140
+ }
141
+ ```
142
+
143
+ Getters: `selectedStructure`, `allowedAlgorithms`, `canReason` (all required anchor/relation slots filled).
144
+ Actions: `loadRegistry()`, `setStructure()`, `reason()`.
145
+
146
+ ### Styling
147
+
148
+ Reuse CSS vars from `styles/tokens.css` (`--primary`, `--primary-soft`, `--motif-stroke`, `--motif-fill`, `--surface`, `--text-muted`, `--shadow-md`). SVG nodes mirror `FloatingMotif` stroke/fill tokens. Dropdowns use Fomantic `.ui.search` + a custom result list driven by Vue. Timing bars use `.ui.progress` overridden to green palette.
149
+
150
+ ## Critical files to modify
151
+
152
+ - **Create**: all files under `src/frontend/src/components/coins/`, `src/frontend/src/views/demos/CoinsView.vue`, `src/frontend/src/stores/coinsDemo.js`.
153
+ - **Edit**: `src/frontend/src/api/coins.js` (add six endpoint wrappers), `src/frontend/src/router/index.js` (add `/demos/coins` route), `src/frontend/src/components/layout/NavBar.vue` (add "Demos" link), `src/frontend/src/views/HomeView.vue` or the COINs `DemoPreviewCard` instance (drop "coming soon", add `to="/demos/coins"`).
154
+
155
+ ## Reusable references
156
+
157
+ - `src/frontend/src/api/client.js` β€” axios instance + `apiError()`.
158
+ - `src/frontend/src/api/coins.js:1-13` β€” existing `sampleTriples`; the new functions follow the same shape.
159
+ - `src/frontend/src/components/home/FactOfTheDay.vue` β€” canonical loading/error/success pattern (mirror it for the predict flow and dropdown fetches).
160
+ - `src/frontend/src/components/background/FloatingMotif.vue` β€” SVG node/edge/arrow styling, CSS-var palette, circular-ish layout scaffold to adapt per structure.
161
+ - `src/frontend/src/components/layout/PageSection.vue` β€” wraps the query-builder and dashboard blocks on the demo view.
162
+ - `src/frontend/src/components/home/SystemStatus.vue` β€” parallel-fetch + badge-status pattern for the registry-load step.
163
+ - `src/frontend/src/styles/tokens.css` β€” palette tokens.
164
+ - `docs/api.yaml` β€” authoritative request/response shapes (keep open while wiring `predict`).
165
+
166
+ ## Implementation Steps
167
+
168
+ 1. **API layer**: extend `src/frontend/src/api/coins.js` with `listDatasets`, `searchEntities`, `searchRelations`, `listModels`, `listQueryStructures`, `predict`. Thread `AbortSignal` through each. Manual smoke test via browser devtools against local backend.
169
+ 2. **Pinia store**: create `src/frontend/src/stores/coinsDemo.js` with state/getters/actions described above. Add `loadRegistry()` firing the three parallel fetches.
170
+ 3. **Router + nav**: register `/demos/coins` in `router/index.js` (lazy-loaded). Add "Demos" link to `NavBar.vue`. Point the COINs `DemoPreviewCard` on Home to `/demos/coins` and drop the "Coming soon" label.
171
+ 4. **Shared `QueryGraph.vue`**: renders any structure (node+edge spec in β†’ SVG out) with per-node and per-edge slots and a small hard-coded coord map for the 7 structures. Re-use `FloatingMotif`'s arrow marker and CSS vars.
172
+ 5. **`QueryStructurePicker.vue`**: grid of cards, each showing a mini `QueryGraph` (no slots, labels-only) + the structure's `name` and `description`. Emits the chosen ID.
173
+ 6. **`SearchableEntityDropdown.vue` / `SearchableRelationDropdown.vue`**: controlled input with 250 ms debounce, axios cancel on keystroke, "Load more" pager. Fomantic `.ui.search` styling. Emit `{id, name, label}`; display `label || name`. Pills size to content per the responsive rules above.
174
+ 7. **`AlgorithmSelector.vue`**: plain dropdown, options derived from `store.allowedAlgorithms` getter. Shows `name` with `description` tooltip.
175
+ 8. **`CoinsView.vue`**: composes the top selector row (dataset + structure picker + algorithm + top-K slider + "Prefill random" link for `1p`), the main `QueryGraph` in edit mode (slots wired to the dropdowns), and the bottom "Reason" button that calls `store.reason()`. Handles the three error classes with banner components.
176
+ 9. **Dashboard panels**: `QueryDescription.vue`, `PredictionList.vue`, `CommunityRankCallout.vue`, `TimingPanel.vue`. Compose them in `ResultsDashboard.vue`. Bars via `.ui.progress` with inline-style widths proportional to `total_ms` (or `baseline_estimate_ms` as the denominator, whichever is larger, for a fair visual).
177
+ 10. **Responsive + dark-mode pass**: verify ≀768 px stacks vertically; SVG viewBox scales; dropdowns full-width on mobile; reflow kicks in below 600 px. Toggle `data-theme="dark"` and confirm palette.
178
+ 11. **Polish**: accessibility labels on every SVG overlay (`aria-label`), `prefers-reduced-motion` respected, keyboard navigation through dropdowns.
179
+
180
+ ## Verification
181
+
182
+ 1. Start the backend locally on the GPU box: `python src/backend/manage.py runserver 0.0.0.0:8000` (checkpoints download on first boot). Confirm `GET http://localhost:8000/api/v1/health` returns all datasets loaded.
183
+ 2. Run the frontend: `cd src/frontend && npm run dev`. Open `http://localhost:5173/demos/coins`.
184
+ 3. For each of the 7 query structures (`1p, 2p, 3p, 2i, 3i, ip, pi`):
185
+ - Pick dataset (rotate through `wordnet`, `freebase`, `nell`).
186
+ - Verify `AlgorithmSelector` shows only compatible algorithms (only `q2b` for multi-hop; all for `1p`).
187
+ - Fill anchors and relations via the searchable dropdowns (verify `?q=` filtering and pagination).
188
+ - For structures with variables (`2p/3p/ip/pi`), test **both** letting the backend sample (omit key) and pinning a variable.
189
+ - Click "Reason". Dashboard renders `query_description`, exactly `top_k` predictions with monotonic `rank`, non-zero `timing.total_ms`, and a `speedup β‰₯ 1`.
190
+ 4. Seeded-daily prefill on `1p`: click "Prefill with a random triple" β†’ anchor + relation populate from the day's seeded `sample-triples` call.
191
+ 5. 429 path: in one tab hold a long-running `predict`; fire a second in another tab β†’ friendly "retry shortly" banner, no crash.
192
+ 6. 422 path: craft a query with no valid KG answers (e.g., mismatched entity/relation combo) β†’ red banner with `INFERENCE_ERROR` message; no results list rendered.
193
+ 7. Responsive: resize to 375 px width, confirm SVG scales and controls stack. Toggle dark mode, confirm palette. Pick a long-named entity (e.g. NELL `concept_athletecoach_thomaspace`) and verify the pill grows and the graph reflows without overlap.
194
+ 8. Postman: cross-check `POST /coins/predict` request body captured from devtools matches an example from `docs/api.yaml` / `docs/postman/` to confirm field names (`anchors`, `variables`, `relations`, `query_structure`, `top_k`).