7.0 KiB
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/)
./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/)
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 classOPENAI_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 byapi/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 sessionGET /api/connections/status— lists connected services for the current sessionDELETE /api/connections/{serviceType}— removes a service from the session; invalidates the session if no connections remainPOST /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 bothconnection/andsearch/ServiceProvider(interface): base forConnectionProviderandSearchProvider; declaresgetServiceType()Endpoint(enum): API path constants for all external service callsSessionKeys: 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-originSessionConfig: JDBC-backed Spring Session with a persistent cookie (SameSite=Lax,HttpOnly)
connection/ — connecting to and persisting service credentials
ConnectionProviderinterface: extendsServiceProvider; each integrated app implementslogin()and credential checkingConnectionService: auto-discovers providers via Spring injection, dispatches login byServiceTypeConnectionController: stores{serviceType}_CONNECTION_IDinHttpSessionafter login; reads session attributes to build status responses- Entity (
ConnectionEntity) uses Single Table Inheritance — oneconnectionstable 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'sEmbeddingModel; reused by any feature that needs to generate vectorsVectorStoreConfig: Spring bean configuration forPgVectorStore(pgvector-backedVectorStore)- 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)
SearchProviderinterface: extendsServiceProvider; each integrated app implementsgetSearchResults()SearchService: maintains two provider maps — keyword providers and AI providers; routes based onSearchRequest.aiSearchflagSearchController: guards with session check before delegating toSearchServiceSearchRequest: includesaiSearch: boolean— when true, routes to AI provider instead of keyword providerPagedSearchResponse: includes nullablesummaryfield — populated only for AI search results; null for keyword searchHomeboxSearchProvider: keyword search via Homebox API; unchanged from original implementationHomeboxAiSearchProvider: AI search via pgvector similarity; returns ranked items + generated summaryHomeboxSyncService: fetches all Homebox items page by page, embeds them viaEmbeddingService, stores inVectorStore; 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— typedapiFetchwrapper; always sendscredentials: 'include'for session cookies; base URL fromVITE_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,ConnectionStatustypes/search.ts—SearchRequest,SearchResponse,PagedSearchResponsecomponents/connections/—Dashboard,ConnectModal,ServiceCard(connection management UI)components/search/SearchModal— search dialog; posts to/api/searchand renders paged resultscomponents/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_URLpoints to LiteLLM, not OpenAI directly, keeping the underlying model provider configurable spring-ai-starter-vector-store-pgvectorprovidesPgVectorStore; configured inai/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.