Files
nexusAI/docs/services/chat-client.md
2026-04-18 06:41:50 -07:00

12 KiB

Chat Client

Package: @nexusai/chat-client
Location: packages/chat-client
Deployed on: Mini PC 2 (192.168.0.205)
URL: https://nexus.jellystorm.com (behind Authelia SSO)

Purpose

Browser-based chat interface for NexusAI. Communicates exclusively with the orchestration service — no direct access to memory, embedding, or inference services. Served as static files by Caddy on Mini PC 2.

Dependencies

  • react + react-dom — UI framework
  • react-markdown — Markdown rendering in message bubbles and memory viewer
  • uuid — session ID generation
  • vite + @vitejs/plugin-react — build tooling

Build

cd packages/chat-client
npm run dev         # local dev server on port 5173
npm run build       # outputs to dist/ for production

After building, copy dist/ contents to /srv/nexusai on Mini PC 2 for Caddy to serve.

Vite bakes environment variables into the bundle at build time. The .env file is only needed on the machine running the build, not where files are served.

Environment Variables

Variable Required Default Description
VITE_ORCHESTRATION_URL No '' (empty) Orchestration base URL. Leave empty in dev (Vite proxy handles routing). Set to HTTPS domain for production builds.

Development: leave VITE_ORCHESTRATION_URL unset — the Vite proxy routes API requests directly to orchestration, bypassing Caddy and Authelia.

Production build: set before running npm run build:

VITE_ORCHESTRATION_URL=https://nexus.jellystorm.com

Do not set VITE_ORCHESTRATION_URL to the HTTPS domain during local dev. Requests from localhost:5173 to nexus.jellystorm.com will hit Authelia, which returns an HTML login page instead of JSON — causing Unexpected token '<' parse errors in useModels and useSession.

Vite Dev Proxy

vite.config.js proxies API routes directly to the orchestration service during local development, bypassing Caddy and Authelia entirely:

export default defineConfig({
  plugins: [react()],
  server: {
    proxy: {
      '/models':   'http://192.168.0.205:4000',
      '/sessions': 'http://192.168.0.205:4000',
      '/chat':     'http://192.168.0.205:4000',
      '/projects': 'http://192.168.0.205:4000',
      '/episodes': 'http://192.168.0.205:4000',
      '/settings': 'http://192.168.0.205:4000',
      '/health':   'http://192.168.0.205:4000',
    }
  }
});

When adding new top-level routes to the orchestration service, add a matching entry here and in the Caddy config.

Internal Structure

src/
├── api/
│   └── orchestration.js     # All fetch calls to the orchestration service
├── config/
│   └── constants.js         # FALLBACK_MODELS, DEFAULT_MODEL, API_DEFAULTS
├── hooks/
│   ├── useSession.js        # Session list, history loading, active session state
│   ├── useChat.js           # Message sending, SSE streaming, message state
│   ├── useModels.js         # Dynamic model list fetched from /models endpoint
│   ├── useProjects.js       # Project list fetched from /projects endpoint
│   ├── useSettings.js       # Settings fetch + saveSetting helper
│   └── useContextMenu.js    # Right-click context menu position and visibility
├── components/
│   ├── App.jsx              # Root component — layout, shared state, view routing
│   ├── Sidebar.jsx          # Left sidebar — projects, recent chats, navigation
│   ├── ChatWindow.jsx       # Centre panel — message thread and input bar
│   ├── MessageBubble.jsx    # Individual message bubble — renders markdown via react-markdown
│   ├── InfoPanel.jsx        # Right panel — model selector and session metadata (slide-in)
│   ├── SessionModal.jsx     # Modal for session rename, project assignment, delete
│   ├── ProjectModal.jsx     # Modal for project create, edit, delete
│   ├── AllChatsView.jsx     # Full paginated session list with multi-select bulk delete
│   ├── AllProjectsView.jsx  # Project tile grid with create/edit/delete
│   ├── ProjectView.jsx      # Individual project — session list, new chat button
│   ├── MemoryView.jsx       # Paginated, searchable, expandable, deletable episode viewer
│   └── SettingsView.jsx     # Settings — Memory limits, Models (inference params, active
│                            #   model, context window), Service Health, Appearance placeholder
├── index.css                # Global reset, CSS variables, utility classes
└── main.jsx                 # React entry point

SessionList.jsx is superseded by Sidebar.jsx and kept only as a reference.

Layout

The app uses a view-based layout. App.jsx manages a view state string that controls which main panel is rendered. The left sidebar and right info panel are persistent across all views.

┌──────────────────┬──────────────────────────────┐
│     Sidebar      │   Main Area (view-dependent)  │
│  (collapsible)   │                               │
│                  │  chat         → ChatWindow    │
│ + New Chat       │  all-chats    → AllChatsView  │
│ ⊞ View Projects  │  all-projects → AllProjectsView│
│                  │  project      → ProjectView   │
│ PROJECTS ▾       │  settings     → SettingsView  │
│  [tile] [tile]   │  memory       → MemoryView    │
│  All Projects →  │                               │
│                  │                               │
│ RECENT CHATS ▾   │                               │
│  Session 1       │                               │
│  Session 2       │                               │
│  All Chats →     │                               │
│                  │                               │
│ ⚙ Settings       │                               │
└──────────────────┴──────────────────────────────┘

The sidebar collapses to a 48px icon rail. The right InfoPanel slides in from the right using transform: translateX() — hidden by default, toggled via the button in the ChatWindow header.

View Routing

View Component Trigger
'chat' ChatWindow Default; selecting a session; new chat
'all-chats' AllChatsView "All Chats →" or ☰ icon in collapsed rail
'all-projects' AllProjectsView "View Projects" button or ⊞ icon
'project' ProjectView Clicking a project tile in the sidebar
'settings' SettingsView Settings button or ⚙ icon
'memory' MemoryView "Open →" button in Settings → Memory section

activeProject state in App.jsx tracks which project ProjectView is displaying. Set via onSelectProject before navigating to 'project'.

CSS Architecture

Styles follow a hybrid approach — CSS utility classes for static reusable rules, inline styles for dynamic prop-driven values.

CSS Variables (:root)

Variable Value Description
--bg-base #0f1117 Page background
--bg-surface #0e0d0d Panel backgrounds
--bg-elevated #222536 Elevated elements (inputs, cards)
--border #2e3150 Border colour
--accent #3d3a79 Primary accent (buttons, highlights)
--accent-hover #574fd6 Accent hover state
--text-primary #e8e8f0 Primary text
--text-secondary #8b8fa8 Secondary text
--text-muted #555870 Muted / placeholder text
--bubble-user #4742a8 User message bubble background
--bubble-ai #20264d AI message bubble background
--sidebar-width 180px Expanded sidebar width
--panel-width 200px Expanded info panel width
--header-height 40px Shared header height across all panels
--radius-sm 6px Small border radius
--radius-md 8px Medium border radius
--radius-lg 12px Large border radius

Utility Classes

Class Description
.panel-header Shared header row — used across all panels
.btn-reset Resets button styles (no border, bg, cursor pointer)
.btn-icon Icon button with hover state
.btn-primary Accent-coloured action button with :hover and :disabled states
.flex / .flex-col Flex layout helpers
.flex-1 / .flex-shrink Flex sizing helpers
.items-center / .justify-center / .justify-between Alignment helpers
.overflow-hidden / .scroll-y Overflow helpers
.text-xs / .text-sm / .text-base Font size helpers
.text-muted / .text-secondary / .text-accent Colour helpers
.label-upper Uppercase section label style
.truncate Text overflow ellipsis

Streaming

Messages are sent via POST /chat/stream. Tokens arrive as SSE events and are written into the active assistant bubble token by token via updateLastMessage. The blinking cursor in MessageBubble is shown while message.streaming === true.

useChat accepts an optional projectId parameter in sendMessage. After the first message completes in a new session, if projectId is set, updateSession is called to write the project assignment to the backend.

Session Management

Sessions are identified by external_id — a UUID generated client-side via the uuid package. New sessions are created locally and auto-registered in the memory service on the first message. The session list refreshes after each completed response to surface newly created sessions.

Auto-naming

After the first exchange completes, orchestration fires a secondary inference call with a short naming prompt (max 20 tokens, temperature 0.3). The result is written back as session.name. The client fires a second refreshSessions after a 3-second delay to pick up the name once written.

Manually renamed sessions are never overwritten — the !session.name guard in chat/index.js prevents this.

Session Actions

Session rows support rename, project assignment, and delete via:

  • Hover — reveals ✎ and ✕ icon buttons alongside the row
  • Right-click — context menu with the same actions

SessionModal handles rename and project assignment together in settings mode, and delete confirmation in confirm-delete mode.

Active Session Clearing on Delete

When the deleted session is the currently active one, App.jsx clears the chat window before refreshing the list:

function handleSessionsChange(deletedSession) {
    if (deletedSession?.external_id === activeSession?.external_id) {
        selectSession(null);
    }
    refreshSessions();
}

Key Patterns

  • Button nesting: action icons are siblings of row buttons, not children — HTML forbids <button> inside <button>
  • Context menu rendered outside sidebar via React fragment to avoid overflow: hidden clipping
  • useContextMenu dismisses on a window click listener
  • Dynamic updateSession SQL builds SET clause from only the fields passed — prevents accidental overwrites

Project Management

useProjects fetches the project list from GET /projects on mount and exposes refreshProjects for keeping the sidebar in sync after mutations.

ProjectModal handles create, edit, and delete confirmation. Fields: name (required), description (optional), colour picker, isolated toggle.

ProjectView shows the project's name, description, isolated badge (if set), and a filtered session list. The "+ New Chat" button creates a new session, navigates to 'chat', and writes the project assignment after the first message.

For memory isolation behaviour, see memory-isolation.md.

Settings

useSettings fetches from GET /settings on mount and exposes a saveSetting(key, value) helper that issues a PATCH /settings with a single key-value pair. The saving boolean is exposed for disabling save buttons during in-flight requests.

SettingsView is organised into sections:

  • Memory — recent episode limit, semantic limit, score threshold, link to MemoryView
  • Models — models folder path, temperature, repeat penalty, Top-P, Top-K, active model dropdown, read-only model info panel (file, size, context window, loaded model from llama-server)
  • About — service health check panel, version
  • Appearance — theme (coming soon)