Files
nexusAI/docs/services/chat-client.md
2026-04-13 03:42:14 -07:00

8.6 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
  • uuid — session ID generation
  • vite + @vitejs/plugin-react — build tooling

Build

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

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.

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

Environment Variables

Variable Required Default Description
VITE_ORCHESTRATION_URL No '' (empty) Orchestration base URL. Must be set to the HTTPS domain in production to avoid mixed content errors.

Production value:

VITE_ORCHESTRATION_URL=https://nexus.jellystorm.com

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
│   └── useContextMenu.js   # Right-click context menu position and visibility
├── components/
│   ├── App.jsx              # Root component — layout and shared state
│   ├── SessionList.jsx      # Left sidebar — session list, rename, delete
│   ├── ChatWindow.jsx       # Centre panel — message thread and input bar
│   ├── MessageBubble.jsx    # Individual message bubble (user or assistant)
│   ├── InfoPanel.jsx        # Right panel — model selector and session metadata
│   └── SessionModal.jsx     # Modal dialog for session settings (rename)
├── index.css                # Global reset, CSS variables, utility classes
└── main.jsx                 # React entry point

Layout

Three-panel layout with collapsible sidebars:

┌─────────────────┬──────────────────────────┬─────────────┐
│  Session List   │       Chat Window         │  Info Panel │
│  (collapsible)  │                           │ (collapsible)│
│                 │  [message thread]         │             │
│ + New Chat      │                           │ Model       │
│                 │                           │ Session ID  │
│ Session 1       │                           │ Token count │
│ Session 2       │                           │             │
│                 │  [input bar]              │             │
└─────────────────┴──────────────────────────┴─────────────┘

Sidebars collapse to a 56px icon rail. The centre chat window always fills the remaining space.

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 #1a1d27 Panel backgrounds
--bg-elevated #222536 Elevated elements (inputs, cards)
--border #2e3150 Border colour
--accent #6c63ff 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 #6c63ff User message bubble background
--bubble-ai #222536 AI message bubble background
--sidebar-width 280px Expanded sidebar width
--panel-width 260px Expanded info panel width
--header-height 56px 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 in all three 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

API Layer

All orchestration calls are centralised in src/api/orchestration.js:

Function Method Path Description
fetchSessions GET /sessions Load session list for sidebar
fetchSessionHistory GET /sessions/:id/history Load episode history on session select
sendMessage POST /chat Send message, await full response
streamMessage POST /chat/stream Send message, receive SSE token stream
fetchModels GET /models Load available models from manifest
renameSession PATCH /sessions/:id Rename a session
deleteSession DELETE /sessions/:id Delete a session

streamMessage returns an abort function — call it to cancel a stream mid-flight. Uses a buffer pattern to handle SSE chunks that may span multiple network packets.

Streaming

The chat input sends messages via POST /chat/stream. Tokens arrive as SSE events:

data: {"text":"Hello"}
data: {"text":" Tim"}
data: {"done":true,"model":"gemma-4-26B-A4B-Claude-Distill-APEX-I-Mini.gguf","tokenCount":87}

An empty assistant bubble is appended immediately when the stream opens, then updated token by token using updateLastMessage. The blinking cursor in MessageBubble is shown while message.streaming === true and disappears when the done event is received. Model name and token count from the done event are stored in useChat state and displayed in the InfoPanel.

Dynamic Model Selector

Available models are fetched from GET /models on mount via the useModels hook. The hook initialises with FALLBACK_MODELS from constants.js and replaces them with the server response on success. If the fetch fails, the fallback list is used silently — a warning is logged to the console.

// constants.js
export const FALLBACK_MODELS = [
  { value: 'companion:latest', label: 'Companion' },
  // ...
];

The selected model is passed with every chat request. To add a model, update models.json on the main PC — no client rebuild needed.

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.

Session Actions

The session list supports rename and delete:

  • Hover — reveals ✎ (rename) and ✕ (delete) icon buttons on the session row
  • Right-click — opens a context menu with the same actions

Rename opens a SessionModal dialog. The modal is designed to expand into a full session settings panel in future — the title is already "Session Settings" to reflect this intent.

Delete is immediate with no confirmation dialog (planned for a future update).

Actions are disabled on unsaved (new) sessions that haven't had a message sent yet.

Context Menu

Implemented via useContextMenu hook — tracks { x, y, session } state and attaches a window click listener to dismiss on any outside click. Rendered outside the sidebar div (via React fragment) to avoid being clipped by overflow: hidden.