CLAUDE.md 7.3 KB

CLAUDE.md — frontend

Angular 20 SPA. Multi-tenant dashboard with a lazy-loaded src.palm.vision sub-app for MPOB-standard palm oil fruit bunch (FFB) ripeness detection. Three inference engines: browser ONNX WASM, NestJS remote (WebSocket), and n8n edge passthrough.

Commands

npm start                   # Dev server at 0.0.0.0:4200
npm run build               # Production build → dist/fisapp-ui
npm run build:dev           # Dev build → dist/dev
npm run build:prod          # Prod build → dist/rc
npm run build:leave:prod    # Leave-module variant
npm run build:quot:prod     # Quotation variant
npm run build:maf:quot:prod # MAF quotation variant
npm run test                # Karma + Jasmine
npm run clean               # Clear Angular + npm caches

Architecture

Application Shell (src/app/)

  • app.config.tsApplicationConfig: bootstraps NGXS root store (withNgxsStoragePlugin, withNgxsReduxDevtoolsPlugin), Angular Router (hash location), HTTP client, service worker.
  • app.routes.ts — Root routes: dashboard, auth, leave, tender, src.palm.vision (all lazy-loaded except dashboard).
  • app.component.ts — Root component: session timeout, PWA install prompt, theme management.

PalmVision Sub-App (src/src.palm.vision/)

Lazy-loaded at /src.palm.vision. Provides its own NGXS VisionState via provideStates([VisionState]) in the feature module.

Sub-routes:

Path Component Purpose
analyzer (default) AnalyzerComponent Inference UI — file/camera input, bounding box canvas
vault HistoryComponent Batch history vault with canvas thumbnails
chat ChatbotComponent Industrial Intelligence Portal (n8n chat proxy)

Services (src/src.palm.vision/services/)

InferenceService — Local WASM inference:

  • analyze(file, batchId?)Observable<InferenceFrame> — preprocesses image → posts tensor to Web Worker → emits result
  • preprocessImage(dataUrl) — resizes to 640×640, converts RGBA→CHW, normalizes [0.0, 1.0]
  • results$: Subject<InferenceFrame> — all completed frames
  • queueDepth$: BehaviorSubject<number> — pending worker frames

RemoteInferenceService — WebSocket bridge to NestJS backend:

  • analyze(file, sourceLabel?, batchId?)Observable<InferenceFrame> — sends PalmVision:analyze via FIS envelope
  • getHistory()Observable<any[]>History:getAll
  • deleteRecord(archiveId)Observable<{ deleted: boolean }>History:delete
  • clearHistory()Observable<{ deleted: number }>History:clearAll
  • getImage(archiveId)Observable<{ archiveId, image_data }>PalmHistory:GetImage
  • saveExternalResult(payload)Observable<any>PalmHistory:SaveExternalResult

Connection config loaded from src/src.palm.vision/config/config.json:

{ "connection": { "uacp": "http://localhost:3000", "uacp_ws": "ws://localhost:3000/socket.io", "uacpEmulation": "on" } }

State (src/src.palm.vision/store/)

VisionStateModel:

{ items: any[]; loading: boolean; expandedBatchIds: string[]; currentInference: InferenceFrame | null }

Selectors: VisionState.items, VisionState.loading, VisionState.expandedBatchIds, VisionState.currentInference

Actions (vision.actions.ts):

Action Payload Effect
SubmitBatchAnalysis { files: File[]; mode: 'local'\|'remote'\|'n8n' } Run batch inference
ToggleBatchGroup { batchId: string } Expand/collapse history accordion
LoadGroupImages { batchId: string } Lazy-load archived images for a batch
LoadHistory Fetch last 50 records from SQLite
DeleteHistoryRecord { id: string } Delete record + disk image
ClearAllHistory Wipe all records

Components

AnalyzerComponent — Three-mode inference UI:

  • Engine selector: local (WASM), remote (NestJS), n8n (edge)
  • Input: drag-and-drop file zone or live webcam (getUserMedia, environment-facing)
  • Canvas overlay via renderPredictionsWithBoxes(frame) — draws bounding boxes + confidence labels
  • MPOB color palette: Ripe #4caf50, Unripe #ff9800, Underripe #ffeb3b, Overripe #9c27b0, Abnormal #f44336, Empty_Bunch #607d8b

HistoryComponent — Batch vault:

  • Groups items by batch_id into BatchGroup[] via combineLatest([items$, expandedBatchIds$])
  • @ViewChildren('thumbCanvas') — canvas thumbnails rendered via renderThumbnailWithBoxes() using norm_box coordinates
  • paintAllVisibleCanvases() with setTimeout(0) deferral; data-archive-id attribute maps canvas elements to data items
  • Mode badge variants: local (WASM), remote (Server), n8n

ChatbotComponent — Thin wrapper around angularlib/chat/chat.component with title "Industrial Intelligence Portal".

Web Worker (src/src.palm.vision/workers/inference.worker.ts)

Receives { frameId, batchId, imageDataUrl, tensor, processingStart }, runs ONNX inference, returns InferenceFrame. Keeps inference off the main thread for WASM local mode.

Key Interfaces

interface DetectionResult {
  bunch_id: number;
  class: string;              // MPOB class name
  confidence: number;         // [0, 1]
  is_health_alert: boolean;   // true if Abnormal | Empty_Bunch
  box: [x1, y1, x2, y2];     // pixel coords
  norm_box?: [nx1, ny1, nx2, ny2]; // normalized [0,1] coords
}

interface InferenceFrame {
  frameId: string;
  batchId?: string;
  imageDataUrl: string;
  detections: DetectionResult[];
  inference_ms: number;
  processing_ms: number;
  total_count: number;
  industrial_summary: Record<string, number>;
  source: 'wasm-local' | 'remote' | 'n8n';
}

Shared In-Source Dependencies (src/dependencies/)

Alias Path Contents
angularlib/* src/dependencies/angularlib/ UI components, auth, forms, chat
dp-ui/* src/dependencies/dp-ui/ Custom Material extensions, NgxSocketService
fis/* src/dependencies/fis/ Domain modules (leave, tender, approval)
fis-commons/* src/dependencies/fis-commons/ CDN-hosted shared utilities

Models & WASM Assets

  • src/assets/models/onnx/best.onnx — YOLOv8 ONNX model for browser WASM inference
  • src/assets/models/tflite/best_float16.tflite / best_float32.tflite — TFLite variants
  • src/assets/wasm/ — ONNX Runtime JS WASM bundles
  • src/assets/tflite-wasm/ — TensorFlow Lite WASM runtime

Multi-Build Strategy

angular.json defines four production configurations that swap assets and menu via fileReplacements:

Config Output Swaps
production dist/rc
leave-prod dist/leave menu.ts → menu.leave.ts, leave assets
quotation-prod dist/quotation menu.ts → menu.quotation.ts, quotation assets
maf-quot-prod dist/maf/quotation MAF quotation assets

Development build uses a mock socket service (dp.service.t.ts) for offline development.

PWA

Service worker registered immediately (registerImmediately) via ngsw-config.json. PWA install prompt handled in AppComponent. Three language packs: en_US, ms_MY, zh_Hans.

CORS / Connection

Frontend connects to NestJS backend at http://localhost:3000 (configurable via config.json). Backend CORS whitelist is hardcoded in server-desktop/src/main.ts — add new device IPs there.