Skip to content

kiSchlag/Sam-Frontend

Repository files navigation

React TypeScript Vite Tailwind CSS DaisyUI

Sam — AI-Powered Diagnostic Frontend for Siemens S7-1500 PLCs


Overview

Sam is a React + TypeScript single-page application serving as the frontend for an AI-powered diagnostic assistant targeting Siemens S7-1500 programmable logic controllers. The interface is entirely in German, reflecting its deployment context in industrial manufacturing environments. The corresponding backend handles retrieval-augmented generation, pipeline orchestration, and incident persistence.

When a plant engineer encounters a cryptic PLC fault code during a production shift, the typical recourse — searching through hundreds of pages of Siemens technical documentation — is slow and error-prone. Sam provides precise, context-aware diagnostic guidance drawn directly from indexed technical manuals rather than generic search results.

The frontend opens a streaming diagnostic session over Server-Sent Events, visualizes a 14-stage AI inference pipeline in real time, and renders each response as sanitized Markdown with inline citation badges linking back to source documents. Every completed diagnosis is stored as a traceable incident with structured operator feedback. The system follows the Retrieval-Augmented Generation pattern — the AI grounds its responses in retrieved documentation rather than relying on parametric knowledge alone.


Tech Stack

Layer Technology
UI Framework React 19.2.4 with TypeScript 5.9.3
Build Tool Vite 7.3.1 (@vitejs/plugin-react)
Styling Tailwind CSS 4.2.0 + DaisyUI 5.5.18
Routing React Router DOM 7.13.0 (lazy-loaded pages)
Markdown marked 17.0.3 + dompurify 3.3.1
PDF Viewer react-pdf 10.4.1
Linting ESLint 9 + Prettier
Language TypeScript 5.9.3 in strict mode

Key Capabilities

Category Detail
Chat Real-time SSE streaming with token-by-token rendering via connectSSE, 14-stage pipeline visualization through usePipelineProgress, AbortController integration for instant stream cancellation
Chat Multi-turn conversation with thread_id context retention, clarification loop via connectResume on /api/diagnose/resume
RAG Inline citation badges extracted by transformCitations from [Quelle: ...] patterns, source deduplication across inline and footer citations
RAG DocumentDrawer with PDF preview via presigned URLs from /api/documents/:id/url, rendered with react-pdf and multi-page navigation
Safety Safety-critical message detection via keyword scan (sicherheitshinweis, safety notice) in useChat, animated SafetyBanner with f-system and electrical variants
Sessions Server-persisted chat sessions via /api/chat/sessions, ChatSidebar navigation, status tracking (active, resolved, documented), context retention across page reloads
Incidents Automatic incident creation from completed diagnoses, feedback collection (confirmed, corrected, rejected), incident-to-chat navigation via session_id
Patterns Aggregation by station, event ID, and module slot via /api/patterns/:plantId, configurable time range, PatternDashboard with frequency and temporal views
Auth JWT Bearer token authentication, email verification via 6-digit code, password reset flow, ProtectedRoute and GuestRoute guards
Multi-Plant Plant selector with PlantContext, optimistic profile update via updateProfile, useLocalStorage fallback for unauthenticated state
Performance React.lazy + Suspense for every page component, AbortController on every async operation, useDeferredValue for resize-safe rendering
Resilience ErrorBoundary wrapper on all layouts, global 401 interception with auto-redirect in api-client.ts, HealthIndicator polling backend status every 30 seconds

Architecture

The codebase follows a 5-layer numbered folder architecture where higher-numbered layers may import from lower-numbered layers but never the reverse. This enforces unidirectional dependencies and keeps domain logic isolated from presentational concerns.

Component Layer System

graph TD
    APP["App.tsx<br/>Context Providers"] --> ROUTER["routes.tsx<br/>Router Config"]
    ROUTER --> PAGES["05-pages<br/>Route-level components (lazy-loaded)"]
    PAGES --> LAYOUTS["04-layouts<br/>AuthLayout - MainLayout - ChatLayout"]
    LAYOUTS --> FEATURES["03-features<br/>chat - incidents - patterns"]
    FEATURES --> SHARED["02-shared<br/>hooks - context - utils - constants"]
    SHARED --> UI["01-ui<br/>Button - Card - Modal - MarkdownContent"]
    TYPES["types/<br/>Shared TypeScript Interfaces"] -.-> PAGES
    TYPES -.-> FEATURES
    TYPES -.-> SHARED

    style APP fill:#6366f1,stroke:#4f46e5,color:#fff
    style ROUTER fill:#8b5cf6,stroke:#7c3aed,color:#fff
    style PAGES fill:#ec4899,stroke:#db2777,color:#fff
    style LAYOUTS fill:#f59e0b,stroke:#d97706,color:#fff
    style FEATURES fill:#10b981,stroke:#059669,color:#fff
    style SHARED fill:#3b82f6,stroke:#2563eb,color:#fff
    style UI fill:#06b6d4,stroke:#0891b2,color:#fff
    style TYPES fill:#6b7280,stroke:#4b5563,color:#fff
Loading

Data Flow

graph LR
    A([User Input<br/>ChatInput]) --> B[useChat<br/>sendMessage]
    B --> C[connectDiagnose<br/>calls connectSSE]
    C --> D{POST /api/diagnose<br/>SSE Stream}
    D -->|metadata| E[Store thread_id<br/>and session_id]
    D -->|node_transition| F[usePipelineProgress<br/>Progress Bar]
    D -->|token| G[Token Accumulation<br/>in Message State]
    D -->|content_replace| G
    D -->|sources| H[Source Documents<br/>Array]
    D -->|clarification| I{ClarificationPrompt<br/>with thread_id}
    D -->|done| J[Finalize with<br/>incident_id]
    G --> K[MarkdownContent<br/>marked + DOMPurify<br/>+ transformCitations]
    H --> L[SourceCitations<br/>Badge Buttons]
    L --> M([DocumentDrawer<br/>PDF via presigned URL])

    style A fill:#6366f1,stroke:#4f46e5,color:#fff
    style B fill:#3b82f6,stroke:#2563eb,color:#fff
    style C fill:#3b82f6,stroke:#2563eb,color:#fff
    style D fill:#f59e0b,stroke:#d97706,color:#fff
    style K fill:#10b981,stroke:#059669,color:#fff
    style L fill:#10b981,stroke:#059669,color:#fff
    style M fill:#06b6d4,stroke:#0891b2,color:#fff
    style F fill:#8b5cf6,stroke:#7c3aed,color:#fff
    style I fill:#ef4444,stroke:#dc2626,color:#fff
Loading

Streaming Protocol

The frontend uses a custom connectSSE function that opens a POST-based SSE connection with JWT authentication, parses event: / data: frames from the response body stream using ReadableStream, and dispatches typed handler callbacks defined in the SSEHandlers interface.

Event Handler UI Action
metadata onMetadata Stores thread_id and session_id for conversation continuity
node_transition onNodeTransition Updates pipeline progress bar — enter adds to active set, exit moves to completed set
token onToken Appends content string to the last assistant message for real-time token streaming
content_replace onContentReplace Replaces entire assistant message content, used for the final formatted response
sources onSources Stores source documents array for citation rendering in SourceCitations
clarification onClarification Pauses streaming, shows ClarificationPrompt with thread_id for resumption via /api/diagnose/resume
done onDone Finalizes message with incident_id, attaches sources, runs safety-critical content detection
error onError Displays error message in ErrorMessage component, stops streaming indicator

Routes

Route Page Auth Layout
/login LoginPage GuestRoute AuthLayout
/register RegisterPage GuestRoute AuthLayout
/verify-email VerifyEmailPage GuestRoute AuthLayout
/forgot-password ForgotPasswordPage GuestRoute AuthLayout
/reset-password ResetPasswordPage GuestRoute AuthLayout
/ Redirect to /chat ProtectedRoute MainLayout
/incidents IncidentsPage ProtectedRoute MainLayout
/incidents/:id IncidentDetailPage ProtectedRoute MainLayout
/patterns PatternsPage ProtectedRoute MainLayout
/chat ChatPage ProtectedRoute ChatLayout
/chat/:sessionId ChatPage ProtectedRoute ChatLayout

Project Structure

src/
├── types/                              # Shared TypeScript interfaces
│   ├── api.ts                          # ApiError class and HTTP error types
│   ├── auth.ts                         # UserRead, LoginResponse, request types
│   ├── chat-sessions.ts                # Session and message response types
│   ├── common.ts                       # Common shared types
│   ├── diagnosis.ts                    # SSEHandlers, ChatMessage, stream payloads
│   ├── incidents.ts                    # Incident, pattern, health types
│   └── index.ts                        # Barrel export
├── 01-ui/                              # Stateless UI primitives
│   ├── badge.tsx                       # Status badge with variant colors
│   ├── button.tsx                      # DaisyUI-themed button
│   ├── card.tsx                        # Card, CardBody, CardTitle
│   ├── citation-transform.ts          # Regex citation extraction and HTML rewrite
│   ├── icon-button.tsx                 # Icon-only button
│   ├── input.tsx                       # Styled text input
│   ├── markdown-content.tsx            # marked + DOMPurify + citation transform
│   ├── modal.tsx                       # Dialog modal
│   ├── progress-steps.tsx              # Multi-step progress indicator
│   ├── safety-banner.tsx               # Safety-critical warning banner
│   ├── section-heading.tsx             # Section heading component
│   ├── spinner.tsx                     # Loading spinner
│   ├── textarea.tsx                    # Styled textarea
│   └── index.ts                        # Barrel export
├── 02-shared/                          # Cross-cutting concerns
│   ├── components/                     # Reusable composed components
│   │   ├── error-boundary.tsx          # Class-based error boundary with retry
│   │   ├── footer.tsx                  # Page footer
│   │   ├── guest-route.tsx             # Auth guard for guest pages
│   │   ├── health-indicator.tsx        # Polling health dot (30s interval)
│   │   ├── navbar.tsx                  # Top navigation bar
│   │   ├── protected-route.tsx         # Auth guard for protected pages
│   │   ├── scroll-to-top.tsx           # Scroll reset on route change
│   │   ├── toast-container.tsx         # Toast notification display
│   │   └── index.ts
│   ├── constants/                      # Application-wide constants
│   │   ├── brand.ts                    # APP_NAME, APP_VERSION, APP_TAGLINE
│   │   ├── endpoints.ts               # All API endpoint paths
│   │   ├── pipeline-nodes.ts          # 14 pipeline node keys and German labels
│   │   ├── plants.ts                   # Plant selector options
│   │   ├── routes.ts                   # Route path constants
│   │   └── index.ts
│   ├── context/                        # React Context providers
│   │   ├── auth-context.tsx            # JWT auth state and actions
│   │   ├── plant-context.tsx           # Plant selection with optimistic update
│   │   ├── toast-context.tsx           # Toast notification state
│   │   └── index.ts
│   ├── hooks/                          # Generic reusable hooks
│   │   ├── use-debounce.ts             # Debounced value hook
│   │   ├── use-fetch.ts               # Data fetching with loading/error state
│   │   ├── use-local-storage.ts        # localStorage-backed state
│   │   ├── use-scroll-position.ts      # Scroll position tracking
│   │   └── index.ts
│   ├── utils/                          # Utility modules
│   │   ├── api-client.ts              # REST client with global 401 interception
│   │   ├── auth-service.ts            # Auth API functions
│   │   ├── retry.ts                    # Retry utility
│   │   ├── sse-client.ts              # POST-based SSE with ReadableStream
│   │   ├── token-storage.ts           # JWT token persistence
│   │   └── index.ts
│   └── index.ts
├── 03-features/                        # Domain feature modules
│   ├── chat/                           # Chat and diagnosis feature
│   │   ├── components/                 # Chat UI components
│   │   │   ├── assistant-message.tsx   # Assistant message with citations
│   │   │   ├── chat-container.tsx      # Main chat area with drawer
│   │   │   ├── chat-input.tsx          # Message input with send/abort
│   │   │   ├── chat-sidebar.tsx        # Session list sidebar
│   │   │   ├── clarification-prompt.tsx # Follow-up question prompt
│   │   │   ├── document-drawer.tsx     # PDF preview with react-pdf
│   │   │   ├── error-message.tsx       # Error display component
│   │   │   ├── message-list.tsx        # Scrollable message container
│   │   │   ├── pipeline-progress.tsx   # Pipeline stage visualization
│   │   │   ├── safety-disclaimer.tsx   # Safety variant detection
│   │   │   ├── source-citations.tsx    # Citation badge buttons
│   │   │   ├── streaming-indicator.tsx # Typing indicator animation
│   │   │   ├── unified-context-bar.tsx # Context metadata bar
│   │   │   ├── user-message.tsx        # User message bubble
│   │   │   └── index.ts
│   │   ├── chat.service.ts            # connectDiagnose, connectResume
│   │   ├── chat-sessions.service.ts   # Session CRUD operations
│   │   ├── documents.service.ts       # Presigned URL retrieval
│   │   ├── use-chat.ts                # Core chat orchestration hook
│   │   ├── use-chat-incidents.ts      # Auto-fetch incidents from messages
│   │   ├── use-chat-sessions.ts       # Session list management
│   │   ├── use-pipeline-progress.ts   # Pipeline node tracking
│   │   └── index.ts
│   ├── incidents/                      # Incident management feature
│   │   ├── components/
│   │   │   ├── feedback-controls.tsx   # Confirm/correct/reject buttons
│   │   │   ├── incident-card.tsx       # Incident summary card
│   │   │   ├── incident-detail.tsx     # Full incident detail view
│   │   │   ├── incident-filters.tsx    # Station, event, time range filters
│   │   │   ├── incident-list.tsx       # Filterable incident list
│   │   │   └── index.ts
│   │   ├── incidents.service.ts       # Incident REST API calls
│   │   ├── use-incident-feedback.ts   # Feedback submission hook
│   │   ├── use-incidents.ts           # Incident list data hook
│   │   └── index.ts
│   ├── patterns/                       # Pattern analysis feature
│   │   ├── components/
│   │   │   ├── pattern-card.tsx        # Pattern summary with priority
│   │   │   ├── pattern-dashboard.tsx   # Aggregation dashboard
│   │   │   ├── station-frequency.tsx   # Station frequency view
│   │   │   ├── temporal-chart.tsx      # Temporal distribution chart
│   │   │   └── index.ts
│   │   ├── patterns.service.ts        # Pattern analysis REST calls
│   │   ├── use-patterns.ts            # Pattern data hook
│   │   └── index.ts
│   └── index.ts
├── 04-layouts/                         # Structural page wrappers
│   ├── auth-layout.tsx                 # Centered card for guest pages
│   ├── chat-layout.tsx                 # Full-height flex for chat
│   ├── main-layout.tsx                 # Navbar + Footer + ErrorBoundary
│   └── index.ts
├── 05-pages/                           # Route-level page components (all lazy-loaded)
│   ├── chat-page/
│   ├── forgot-password-page/
│   ├── home-page/
│   ├── incident-detail-page/
│   ├── incidents-page/
│   ├── login-page/
│   ├── patterns-page/
│   ├── register-page/
│   ├── reset-password-page/
│   ├── verify-email-page/
│   └── index.ts
├── App.tsx                             # Root component with context providers
├── main.tsx                            # Entry point with StrictMode
├── routes.tsx                          # Router configuration with lazy loading
├── index.css                           # Tailwind + DaisyUI + custom theme
└── vite-env.d.ts                       # Vite type declarations

How Diagnostic Chat Works

1. Connect

When the user types a fault description into ChatInput, the useChat hook's sendMessage function creates two optimistic ChatMessage objects — one with role "user" containing the input text and one empty with role "assistant" as a placeholder for the streamed response. The hook resets the pipeline progress state via usePipelineProgress.reset() and calls connectDiagnose, which invokes connectSSE to open a POST request to /api/diagnose. The request body carries the message, an optional session_id for session continuity, and an optional thread_id for multi-turn context. The connectSSE function attaches the JWT Bearer token from token-storage.ts in the Authorization header. An AbortController signal is threaded through the entire chain — the user can cancel a running stream at any time via the ChatInput abort button, and the hook cleans up gracefully when it catches the AbortError.

2. Stream

The connectSSE function reads the response body via ReadableStream.getReader() and a TextDecoder, accumulating incoming bytes into a line buffer. It splits on newlines and parses event: / data: frames according to the SSE specification — each complete frame consists of an event: line, a data: line carrying a JSON payload, and a blank line delimiter. The dispatchEvent function JSON-parses the data and calls the corresponding typed handler from the SSEHandlers interface.

The node_transition events drive the usePipelineProgress hook, which maintains two Set<string> collections — activeNodes and completedNodes. An enter status adds the node to the active set; an exit status moves it from active to completed. Progress percentage is calculated as (completedNodes.size / PIPELINE_NODE_COUNT) * 100 where PIPELINE_NODE_COUNT is the constant 14, defined in pipeline-nodes.ts. The PipelineProgress component renders each node from PIPELINE_NODE_LABELS with its German label and current status.

The token events append the content string to the last assistant message, producing a real-time token-by-token streaming effect. The content_replace event overwrites the entire message buffer — the backend uses this to deliver the final formatted response after citation enforcement and post-processing. The sources event delivers an array of SourceItem objects containing document, document_id, section, page, and relevance_score.

When the backend requires additional information, it emits a clarification event containing text, thread_id, and requires_response. The useChat hook sets the clarification state, the UI presents a ClarificationPrompt component, and the user's reply triggers connectResume — which opens a new SSE connection to /api/diagnose/resume with the stored thread_id.

3. Render

On done, the MarkdownContent component processes the final response through a three-stage rendering pipeline. First, marked parses the Markdown in GFM mode with breaks: true enabled. Second, DOMPurify.sanitize strips dangerous HTML while preserving allowlisted attributes including data-citation-index. Third, transformCitations applies a regex that matches [Quelle: document, section, S. page] patterns and rewrites each match into a clickable superscript span element with a data-citation-index attribute and a numbered badge.

The stripSourcesFooter function removes the AI-generated "Quellenangaben" section from the rendered HTML because the UI replaces it with a richer SourceCitations component. That component renders numbered badge buttons for each source document, deduplicated against any citations already referenced inline. Clicking any citation badge — whether inline in the text or in the footer component — opens the DocumentDrawer. The drawer calls getDocumentUrl to fetch a presigned URL from /api/documents/:id/url and renders the target PDF page using react-pdf with multi-page navigation support.

The useChat hook also performs safety-critical content detection by scanning the final response for keywords such as sicherheitshinweis and safety notice. When detected, the message is flagged with isSafetyCritical: true, and the SafetyBanner component renders an animated warning with the appropriate variant — f-system for functional safety systems, electrical for electrical hazards, or both when both patterns are present in the response.


Getting Started

Prerequisites

  • Node.js >= 20
  • npm >= 10
  • A running instance of the Sam backend API (default: http://localhost:8000)

Setup

# Clone the repository
git clone <repo-url>
cd s7-1500-frontend

# Install dependencies
npm install

# Configure environment
cp .env.example .env
# Edit .env -- set VITE_API_BASE_URL to the backend address

# Start development server
npm run dev

# Build for production
npm run build

# Preview production build
npm run preview

Development Approach

The application was developed through incremental delivery in clearly scoped phases, each submitted as a single pull request against the main branch. This approach produced a traceable development history where each PR represents an atomic capability addition — from the initial project scaffold through domain features to cross-cutting concerns like authentication and UI refinement.

The layered architecture enforces unidirectional dependencies throughout: 05-pages may import from any lower layer, but 01-ui contains zero domain logic and no imports from higher layers. Each feature module in 03-features is self-contained with its own service layer, hooks, and components, communicating with other features only through shared types and context in 02-shared. This structure enables independent development and review of each feature without cross-contamination.

PR / Phase Scope
PR #1 — Project Scaffold Vite + React + TypeScript project with Tailwind CSS v4 and DaisyUI 5
PR #2 — Shared Layer UI primitives (Button, Card, Modal, Spinner), type foundations, and 02-shared utilities
PR #3 — Navigation and Plant Selection Landing page, MainLayout with Navbar, routing via React Router DOM, PlantContext
PR #4 — Chat and Diagnosis SSE streaming via connectSSE, useChat hook, PipelineProgress, clarification flow
PR #5 — Incident Memory Incident list and detail views, feedback controls (confirmed, corrected, rejected), filters
PR #6 — Pattern Analysis PatternDashboard with aggregation by station, event ID, and module slot
PR #7 — API Integration Frontend-backend communication fixes across all endpoints
PR #8 — Authentication JWT Bearer token auth, AuthProvider, ProtectedRoute / GuestRoute, email verification
PR #9 — Auth Refinement Token handling refactor, registration form simplification
PR #10 — Chat Sessions ChatSidebar with session history, context retention across reloads, incident-to-chat navigation
PR #11 — Citation Overhaul transformCitations with deduplication, inline superscript badges, DocumentDrawer with PDF preview

Configuration

Variable Description
VITE_API_BASE_URL Base URL of the Sam backend API. Defaults to http://localhost:8000 when not set. Available to client-side code via import.meta.env.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages