File size: 5,169 Bytes
175b650
8bf4795
175b650
8bf4795
175b650
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
# Frontend β€” Vue 3 SPA

Single-page application for the PhD-research demo site. Vue 3 + Vite + Pinia + Fomantic UI; talks to the Django backend at `/api/v1/*`. Built once into `dist/` at image-build time and served by WhiteNoise on the same origin as the API in production.

## Tech stack

- **Vue 3** (`^3.5.32`) β€” composition-API components.
- **Vite 8** β€” dev server and production bundler.
- **Vue Router 4** β€” `createWebHistory` mode, lazy-loaded demo views, wildcard 404.
- **Pinia 3** β€” store-per-feature.
- **axios** β€” JSON HTTP client for discovery / non-streaming endpoints.
- **Fomantic UI CSS** β€” visual primitives (buttons, segments, dropdowns, etc.).

## Quick start

From this directory:

```bash
npm install
npm run dev
```

Vite serves on `http://localhost:5173`. The Django backend must also be running on `http://localhost:8000` (see [the local-development guide](../../docs/guides/local-development.md)).

Production build:

```bash
npm run build
# emits dist/, copied into the Docker image at /app/backend/dist
```

## Project layout

```
src/frontend/src/
β”œβ”€β”€ main.js              # bootstrap (Pinia, router, theme)
β”œβ”€β”€ App.vue              # shell: NavBar, RouterView, GraphBackground
β”œβ”€β”€ api/                 # axios client + per-method API wrappers
β”‚   β”œβ”€β”€ client.js
β”‚   β”œβ”€β”€ coins.js
β”‚   β”œβ”€β”€ graphGeneration.js
β”‚   β”œβ”€β”€ kgAnomaly.js
β”‚   └── health.js
β”œβ”€β”€ composables/
β”‚   └── useSseStream.js  # SSE client for streaming inference
β”œβ”€β”€ stores/              # Pinia stores
β”‚   β”œβ”€β”€ coinsDemo.js
β”‚   β”œβ”€β”€ multiproxanDemo.js
β”‚   β”œβ”€β”€ kgAnomalyDemo.js
β”‚   └── theme.js
β”œβ”€β”€ router/index.js      # routes + lazy demo loading + wildcard 404
β”œβ”€β”€ views/               # page-level components
β”‚   β”œβ”€β”€ HomeView.vue
β”‚   β”œβ”€β”€ CVView.vue
β”‚   β”œβ”€β”€ NotFoundView.vue
β”‚   └── demos/{Coins,MultiProxAn,KgAnomaly}View.vue
β”œβ”€β”€ components/          # feature-grouped components (coins/, multiproxan/, kganomaly/, cv/, home/, layout/, common/, background/)
β”œβ”€β”€ data/cv.js           # static CV data
└── styles/              # tokens, theme, responsive helpers
```

For a full walk-through of every store, composable and component group, see [`docs/reference/frontend-modules.md`](../../docs/reference/frontend-modules.md).

## Environment variables

| File | Purpose | Value |
|---|---|---|
| `.env.development` | Dev β€” Vite reads this when running `npm run dev`. | `VITE_API_BASE_URL=http://localhost:8000` |
| `.env.production` | Prod β€” Vite reads this when running `npm run build`. | `VITE_API_BASE_URL=` (empty β†’ same-origin) |

Both `src/api/client.js` and `src/composables/useSseStream.js` compute their base URL as:

```js
`${import.meta.env.VITE_API_BASE_URL ?? 'http://localhost:8000'}/api/v1`
```

The `??` (not `||`) is intentional β€” an empty string from `.env.production` must survive so the deployed SPA hits a same-origin `/api/v1`.

## API conventions

- Discovery endpoints return JSON; the wrappers in `src/api/*.js` parse the response and return `data` directly.
- Streaming endpoints (`/graph-generation/{generate,continue}`, `/kg-anomaly/{correct,continue}`) are consumed via `useSseStream`. Stores wire its `onProgress` / `onPreview` / `onResult` callbacks to mutate state.
- Errors come back as `{ error: { code, message, details } }`. `apiError(error)` (in `client.js`) extracts that envelope from an axios error.

The exact event protocol is in [`docs/reference/sse-protocol.md`](../../docs/reference/sse-protocol.md).

## Routing

| Path | Name | View |
|---|---|---|
| `/` | `home` | `HomeView.vue` |
| `/cv` | `cv` | `CVView.vue` |
| `/demos/coins` | `demo-coins` | `CoinsView.vue` (lazy) |
| `/demos/multiproxan` | `demo-multiproxan` | `MultiProxAnView.vue` (lazy) |
| `/demos/kganomaly` | `demo-kganomaly` | `KgAnomalyView.vue` (lazy) |
| `/:pathMatch(.*)*` | `not-found` | `NotFoundView.vue` (Lara Croft 404) |

In production, the Django backend serves `dist/index.html` for any non-API path so the SPA's history mode keeps working on hard refreshes.

## Theming

`stores/theme.js` toggles a `data-theme` attribute on `<html>` between `light` and `dark`, persists the choice to `localStorage`, and falls back to `prefers-color-scheme`. The actual colors live in `styles/tokens.css` and `styles/theme.css` as CSS custom properties.

## Background animation

`components/background/GraphBackground.vue` renders the floating-graph motif behind every page (the project's visual signature). It's mounted once in `App.vue` and respects `prefers-reduced-motion`.

## See also

- [`docs/reference/frontend-modules.md`](../../docs/reference/frontend-modules.md) β€” full module reference.
- [`docs/reference/sse-protocol.md`](../../docs/reference/sse-protocol.md) β€” the streaming protocol the demo stores parse.
- [`docs/guides/local-development.md`](../../docs/guides/local-development.md) β€” running both servers together.
- [`docs/explanation/architecture.md`](../../docs/explanation/architecture.md) β€” how the SPA is served by the deployed container.