120 lines
7.0 KiB
Markdown
120 lines
7.0 KiB
Markdown
# CLAUDE.md
|
|
|
|
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
|
|
## Project Overview
|
|
|
|
Vaessl is an AI-powered integration bridge that accepts user text/image inputs, processes them through an LLM pipeline (via LiteLLM), and exports structured data to management systems (Homebox, WikiJS). The backend uses a provider pattern for extensibility. The frontend has a working connection management dashboard.
|
|
|
|
## Commands
|
|
|
|
### Backend (Spring Boot + Gradle, inside `backend/`)
|
|
|
|
```bash
|
|
./gradlew build # compile and package
|
|
./gradlew test # run all tests
|
|
./gradlew test --tests com.vaessl.app.connection.ConnectionServiceTest # single test class
|
|
```
|
|
|
|
### Frontend (React + Vite, inside `frontend/`)
|
|
|
|
```bash
|
|
npm run dev # start dev server
|
|
npm run build # TypeScript check + Vite build
|
|
npm run lint # ESLint
|
|
npm run test # Vitest watch mode
|
|
npm run test:ui # Vitest visual dashboard
|
|
```
|
|
|
|
## Environment
|
|
|
|
Copy `.env.local` (not committed) into `backend/` with:
|
|
|
|
- `DB_URL`, `DB_TEST_URL`, `DB_USERNAME`, `DB_PASSWORD` — PostgreSQL (test container on port 5434)
|
|
- `PG_DRIVER_CLASS_NAME` — PostgreSQL JDBC driver class
|
|
- `OPENAI_KEY`, `OPENAI_BASE_URL` — LiteLLM gateway (provider-agnostic, configured for gpt-4o-mini)
|
|
- `FRONTEND_LOCAL_URL`, `FRONTEND_PUBLIC_URL` — allowed CORS origins for the backend
|
|
|
|
Frontend (optional, defaults to `/api`):
|
|
|
|
- `VITE_API_URL` — backend base URL used by `api/client.ts`
|
|
|
|
## Architecture
|
|
|
|
### Backend (`backend/src/main/java/com/vaessl/app/`)
|
|
|
|
Package-by-feature layout. Server context path is `/api`. Main endpoints:
|
|
|
|
- `POST /api/login` — authenticates a service, stores connection ID in session
|
|
- `GET /api/connections/status` — lists connected services for the current session
|
|
- `DELETE /api/connections/{serviceType}` — removes a service from the session; invalidates the session if no connections remain
|
|
- `POST /api/search` — paged search against the requested service; returns 401 if no active session
|
|
|
|
Six packages:
|
|
|
|
**`shared/`** — cross-cutting types used by more than one feature package
|
|
|
|
- `ServiceType` (enum): identifies each integrated app (e.g. `HOMEBOX`); used in both `connection/` and `search/`
|
|
- `ServiceProvider` (interface): base for `ConnectionProvider` and `SearchProvider`; declares `getServiceType()`
|
|
- `Endpoint` (enum): API path constants for all external service calls
|
|
- `SessionKeys`: builds session attribute names of the form `{SERVICE_TYPE}_CONNECTION_ID`
|
|
|
|
**`config/`**
|
|
|
|
- `CorsConfig`: env-driven allowed origins (`FRONTEND_LOCAL_URL`, `FRONTEND_PUBLIC_URL`); `allowCredentials(true)` is required for session cookies to work cross-origin
|
|
- `SessionConfig`: JDBC-backed Spring Session with a persistent cookie (`SameSite=Lax`, `HttpOnly`)
|
|
|
|
**`connection/`** — connecting to and persisting service credentials
|
|
|
|
- `ConnectionProvider` interface: extends `ServiceProvider`; each integrated app implements `login()` and credential checking
|
|
- `ConnectionService`: auto-discovers providers via Spring injection, dispatches login by `ServiceType`
|
|
- `ConnectionController`: stores `{serviceType}_CONNECTION_ID` in `HttpSession` after login; reads session attributes to build status responses
|
|
- Entity (`ConnectionEntity`) uses **Single Table Inheritance** — one `connections` table with app-specific nullable columns
|
|
- `HomeboxConnectionProvider` / `HomeboxEntity`: Homebox-specific implementation
|
|
|
|
**`ai/`** — shared AI infrastructure used by multiple features (search, future image analysis)
|
|
|
|
- `EmbeddingService`: wraps Spring AI's `EmbeddingModel`; reused by any feature that needs to generate vectors
|
|
- `VectorStoreConfig`: Spring bean configuration for `PgVectorStore` (pgvector-backed `VectorStore`)
|
|
- All classes here are provider-agnostic — the OpenAI starter is pointed at LiteLLM, so the underlying model is configurable without code changes
|
|
|
|
**`search/`** — querying connected services (keyword and AI)
|
|
|
|
- `SearchProvider` interface: extends `ServiceProvider`; each integrated app implements `getSearchResults()`
|
|
- `SearchService`: maintains two provider maps — keyword providers and AI providers; routes based on `SearchRequest.aiSearch` flag
|
|
- `SearchController`: guards with session check before delegating to `SearchService`
|
|
- `SearchRequest`: includes `aiSearch: boolean` — when true, routes to AI provider instead of keyword provider
|
|
- `PagedSearchResponse`: includes nullable `summary` field — populated only for AI search results; null for keyword search
|
|
- `HomeboxSearchProvider`: keyword search via Homebox API; unchanged from original implementation
|
|
- `HomeboxAiSearchProvider`: AI search via pgvector similarity; returns ranked items + generated summary
|
|
- `HomeboxSyncService`: fetches all Homebox items page by page, embeds them via `EmbeddingService`, stores in `VectorStore`; triggered on connection login (background sync)
|
|
|
|
**AI search flow:** on login → background sync indexes all Homebox items into pgvector. On AI search → embed query → pgvector similarity search → top N results passed to `ChatClient` for summary generation → return list + summary. Sync is idempotent (delete-then-reindex per connection).
|
|
|
|
**`exception/`** — `GlobalExceptionHandler` via `@ControllerAdvice`
|
|
|
|
### Frontend (`frontend/src/`)
|
|
|
|
React 19 + TypeScript + SCSS, Vite 6 build. Package-by-feature under `components/`.
|
|
|
|
- `api/client.ts` — typed `apiFetch` wrapper; always sends `credentials: 'include'` for session cookies; base URL from `VITE_API_URL` (defaults to `/api`)
|
|
- `api/connections.ts` — connection-specific API calls (`getStatuses`, `login`, `logout`)
|
|
- `api/searches.ts` — search API call (`search`)
|
|
- `types/connection.ts` — `ServiceType`, `LoginRequest`, `AuthResponse`, `ConnectionStatus`
|
|
- `types/search.ts` — `SearchRequest`, `SearchResponse`, `PagedSearchResponse`
|
|
- `components/connections/` — `Dashboard`, `ConnectModal`, `ServiceCard` (connection management UI)
|
|
- `components/search/SearchModal` — search dialog; posts to `/api/search` and renders paged results
|
|
- `components/ui/` — shared UI primitives (`ActionButton`, `Modal`)
|
|
|
|
### Data & AI
|
|
|
|
- PostgreSQL + pgvector (semantic search via embeddings); also used as the Spring Session store (JDBC)
|
|
- LiteLLM as a unified AI proxy; Spring AI OpenAI starter wired to it — `OPENAI_BASE_URL` points to LiteLLM, not OpenAI directly, keeping the underlying model provider configurable
|
|
- `spring-ai-starter-vector-store-pgvector` provides `PgVectorStore`; configured in `ai/VectorStoreConfig`
|
|
- Embedding dimensions must stay consistent with the configured LiteLLM embedding model — changing models requires re-syncing all indexed items
|
|
- Processing pipeline (Phase 2): stage in DB → LLM inference → refine via UI → export to target app
|
|
|
|
### Testing Strategy
|
|
|
|
Integration tests spin up a **mirrored PostgreSQL container** on port 5434 (same schema as production). WireMock mocks external HTTP APIs (Homebox, WikiJS). Do not mock the database in integration tests — the mirrored container strategy exists specifically to catch schema/migration divergence.
|