summary system backend implementation
This commit is contained in:
@@ -98,14 +98,15 @@ src/
|
||||
│ ├── 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
|
||||
│ ├── ProjectModal.jsx # Modal for project create/edit — name, description, colour,
|
||||
│ │ # system prompt override; delete confirmation
|
||||
│ ├── AllChatsView.jsx # Paginated session list with project indicator column
|
||||
│ ├── AllProjectsView.jsx # Project tile grid with create/edit/delete; tile click navigates to ProjectView
|
||||
│ ├── ProjectView.jsx # Individual project — conversations, new chat input, memory
|
||||
│ │ # placeholder, user notes, ⋮ edit/delete menu
|
||||
│ ├── MemoryView.jsx # Paginated, searchable, expandable, deletable episode viewer
|
||||
│ └── SettingsView.jsx # Settings — Memory limits, Models (inference params, active
|
||||
│ # model, context window), Service Health, Appearance placeholder
|
||||
│ └── SettingsView.jsx # Settings — Memory, Models, Behaviour (system prompt),
|
||||
│ # About, Appearance
|
||||
├── index.css # Global reset, CSS variables, utility classes
|
||||
└── main.jsx # React entry point
|
||||
```
|
||||
@@ -151,7 +152,7 @@ in the `ChatWindow` header.
|
||||
|
||||
| View | Component | Trigger |
|
||||
|---|---|---|
|
||||
| `'home'` | `HomeView` | Initial load; going back from chat with no history |
|
||||
| `'home'` | `HomeView` | Initial load |
|
||||
| `'chat'` | `ChatWindow` | Selecting a session; new chat; sending from HomeView |
|
||||
| `'all-chats'` | `AllChatsView` | "All Chats →" or ☰ icon in collapsed rail |
|
||||
| `'all-projects'` | `AllProjectsView` | "View Projects" button or ⊞ icon |
|
||||
@@ -178,10 +179,9 @@ leaving `'home'` expands it.
|
||||
- Centred textarea input — sending creates a new session and navigates to chat
|
||||
- Quick action pills that populate the input without auto-sending
|
||||
|
||||
Sending from HomeView uses `handleHomeSend` in `App.jsx`, which calls
|
||||
`createSession()` (returns the new session object), then immediately calls
|
||||
`sendMessage` with the session passed directly as a parameter — avoiding the
|
||||
React state settling race condition that would cause the message to be dropped.
|
||||
`handleHomeSend` in `App.jsx` calls `createSession()` (which returns the new
|
||||
session object), then immediately calls `sendMessage` with the session passed
|
||||
directly — avoiding the React state settling race condition.
|
||||
|
||||
## CSS Architecture
|
||||
|
||||
@@ -283,6 +283,7 @@ mode, and delete confirmation in `confirm-delete` mode.
|
||||
- `useContextMenu` dismisses on a `window` click listener
|
||||
- Dynamic `updateSession` SQL builds `SET` clause from only the fields passed — prevents accidental overwrites
|
||||
- `AllChatsView` pagination uses `CLIENT_DEFAULTS.PAGE_SIZE` (not `API_DEFAULTS.PAGE_SIZE` which doesn't exist)
|
||||
- `Sidebar` groups sessions by project — `key` must be passed directly to `<SessionRow key={...}>`, not included in the props spread object
|
||||
|
||||
## Sidebar — Session Grouping
|
||||
|
||||
@@ -302,11 +303,27 @@ The isolated toggle has been removed from `ProjectModal`.
|
||||
`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.
|
||||
### ProjectModal Fields
|
||||
|
||||
Clicking a project tile in `AllProjectsView` calls `onSelectProject` then
|
||||
navigates to `'project'`.
|
||||
- **Name** (required)
|
||||
- **Description** (optional)
|
||||
- **Colour** — picker from six preset hex values
|
||||
- **System Prompt** (optional) — overrides the global system prompt for all
|
||||
conversations in this project. Leave blank to use the global default.
|
||||
Stored as `system_prompt` (snake_case) matching the SQLite column.
|
||||
`Enter` key does not submit — textarea fields make it ambiguous. Save button only.
|
||||
|
||||
`handleSave` in `ProjectView` destructures `system_prompt` (snake_case) to
|
||||
match what `ProjectModal` sends. `updateProject` in `orchestration.js` uses
|
||||
a passthrough pattern — spreads all fields into the request body.
|
||||
|
||||
### System Prompt Hierarchy
|
||||
|
||||
System prompt resolution in `chat/index.js` (orchestration):
|
||||
|
||||
1. `project.system_prompt` — if set on the project (highest priority)
|
||||
2. `settings.systemPrompt` — global setting from `settings.json`
|
||||
3. `ORCHESTRATION.SYSTEM_PROMPT` — hardcoded constant in `@nexusai/shared` (last resort)
|
||||
|
||||
### ProjectView
|
||||
|
||||
@@ -322,9 +339,8 @@ navigates to `'project'`.
|
||||
saved value (`savedNotes` state tracks the baseline, not `initialNotes`)
|
||||
|
||||
`updateProject` in `orchestration.js` uses a passthrough pattern — spreads
|
||||
all fields directly into the request body, only transforming `isolated` if
|
||||
present. This allows partial updates like `{ notes }` without clobbering
|
||||
other fields.
|
||||
all fields directly into the request body. This allows partial updates like
|
||||
`{ notes }` or `{ system_prompt }` without clobbering other fields.
|
||||
|
||||
For memory isolation behaviour, see `memory-isolation.md`.
|
||||
|
||||
@@ -337,9 +353,9 @@ buttons during in-flight requests.
|
||||
|
||||
`SettingsView` receives `settings`/`saveSetting`/`saving` from a single
|
||||
`useSettings()` call at the top level and passes them as props to
|
||||
`ModelsSection` and `ModelsFolderSetting` — avoiding triple fetch on mount.
|
||||
`modelProps` (context window, loaded model) is fetched once in `App.jsx` and
|
||||
passed down as a prop, eliminating a duplicate fetch on every settings open.
|
||||
`ModelsSection`, `ModelsFolderSetting`, and `SystemPromptSetting` — avoiding
|
||||
triple fetch on mount. `modelProps` (context window, loaded model) is fetched
|
||||
once in `App.jsx` and passed down as a prop.
|
||||
|
||||
`SettingsView` is organised into sections:
|
||||
|
||||
@@ -347,6 +363,9 @@ passed down as a prop, eliminating a duplicate fetch on every settings open.
|
||||
- **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)
|
||||
- **Behaviour** — global system prompt textarea (`SystemPromptSetting`). Save
|
||||
button appears only when content differs from `savedPrompt` state. Saving an
|
||||
empty string sends `null` which reverts to the hardcoded default.
|
||||
- **About** — service health check panel, version
|
||||
- **Appearance** — theme (coming soon)
|
||||
|
||||
|
||||
@@ -58,7 +58,7 @@ Six core tables:
|
||||
- **entities** — named things the system learns about (people, places, concepts)
|
||||
- **relationships** — directional labeled links between entities
|
||||
- **summaries** — condensed episode groups for efficient context retrieval
|
||||
- **projects** — named groupings of sessions with `name`, `description`, `colour`, `icon`, `isolated`, `notes`
|
||||
- **projects** — named groupings of sessions with `name`, `description`, `colour`, `icon`, `isolated`, `notes`, `system_prompt`
|
||||
|
||||
### Migrations
|
||||
|
||||
@@ -71,6 +71,7 @@ try { db.exec(`ALTER TABLE sessions ADD COLUMN project_id INTEGER REFERENCES pro
|
||||
try { db.exec(`CREATE INDEX IF NOT EXISTS idx_sessions_project ON sessions(project_id)`); } catch {}
|
||||
try { db.exec(`ALTER TABLE projects ADD COLUMN isolated INTEGER NOT NULL DEFAULT 0`); } catch {}
|
||||
try { db.exec(`ALTER TABLE projects ADD COLUMN notes TEXT`); } catch {}
|
||||
try { db.exec(`ALTER TABLE projects ADD COLUMN system_prompt TEXT`); } catch {}
|
||||
```
|
||||
|
||||
New migrations are always appended here — never modify the schema file for
|
||||
@@ -92,29 +93,15 @@ keep the FTS index automatically in sync with the episodes table.
|
||||
|
||||
Both `updateSession` and `updateProject` build their `SET` clause dynamically
|
||||
from only the fields passed — prevents partial updates from overwriting fields
|
||||
that weren't touched:
|
||||
that weren't touched.
|
||||
|
||||
`updateProject` allowlist:
|
||||
```js
|
||||
// updateProject example
|
||||
function updateProject(id, fields = {}) {
|
||||
const allowed = ['name', 'description', 'colour', 'icon', 'isolated', 'notes'];
|
||||
const updates = [];
|
||||
const values = [];
|
||||
for (const key of allowed) {
|
||||
if (fields[key] !== undefined) {
|
||||
updates.push(`${key} = ?`);
|
||||
values.push(fields[key] ?? null);
|
||||
}
|
||||
}
|
||||
if (updates.length === 0) return getProject(id);
|
||||
values.push(id);
|
||||
db.prepare(`UPDATE projects SET ${updates.join(', ')} WHERE id = ?`).run(...values);
|
||||
return getProject(id);
|
||||
}
|
||||
const allowed = ['name', 'description', 'colour', 'icon', 'isolated', 'notes', 'system_prompt'];
|
||||
```
|
||||
|
||||
This means saving just `{ notes: "..." }` won't touch `name`, `colour`, or
|
||||
any other field.
|
||||
This means saving just `{ notes: "..." }` or `{ system_prompt: "..." }` won't
|
||||
touch any other field.
|
||||
|
||||
## Qdrant / Semantic Layer
|
||||
|
||||
@@ -183,8 +170,10 @@ After extraction, each entity is:
|
||||
Qdrant collection with `{ name, type, notes, projectId }` as payload —
|
||||
`projectId` scopes entities to their project for isolated retrieval
|
||||
|
||||
The Qdrant payload stores enough information to reconstruct entity context
|
||||
at retrieval time without a SQLite roundtrip.
|
||||
`extractAndStoreEntities` receives `projectId` from `createEpisode`, which
|
||||
receives it from the episode route, which receives it from orchestration's
|
||||
`createEpisode` call. This ensures entities are tagged with the correct
|
||||
project scope at extraction time.
|
||||
|
||||
## Project Delete Behaviour
|
||||
|
||||
|
||||
@@ -48,7 +48,7 @@ src/
|
||||
├── routes/
|
||||
│ ├── chat.js # POST /chat and POST /chat/stream
|
||||
│ ├── sessions.js # Session CRUD proxy
|
||||
│ ├── projects.js # Project CRUD proxy
|
||||
│ ├── projects.js # Project CRUD proxy — passes req.body straight through
|
||||
│ ├── episodes.js # Episode list and delete proxy
|
||||
│ ├── settings.js # GET /settings and PATCH /settings
|
||||
│ ├── health.js # GET /health — pings all four services
|
||||
@@ -75,6 +75,7 @@ via `appSettings.load()` — changes apply immediately without a service restart
|
||||
| `repeatPenalty` | 1.1 | Repeat token penalty |
|
||||
| `topP` | 0.9 | Nucleus sampling probability mass |
|
||||
| `topK` | 40 | Top-K token candidates per step |
|
||||
| `systemPrompt` | *(ORCHESTRATION.SYSTEM_PROMPT)* | Global system prompt. `null` reverts to hardcoded constant. |
|
||||
|
||||
Defaults are defined in `config/settings.js` and fall back to constants in
|
||||
`@nexusai/shared`. Values saved in `settings.json` take precedence.
|
||||
@@ -91,41 +92,43 @@ difference is how the inference response is delivered to the client.
|
||||
step needed.
|
||||
|
||||
2. **Project context resolution** — if the session has a `project_id`, fetch
|
||||
the project and all its session IDs. Used to scope semantic search. See
|
||||
`memory-isolation.md` for full behaviour.
|
||||
the project and all its session IDs. Used to scope semantic search. The
|
||||
project's `system_prompt` is also read at this step if set.
|
||||
|
||||
3. **Recent episode retrieval** — fetch the most recent episodes for the
|
||||
3. **System prompt resolution** — three-tier hierarchy:
|
||||
- `project.system_prompt` — if the session is in a project and it's set (highest priority)
|
||||
- `settings.systemPrompt` — global setting from `settings.json`
|
||||
- `ORCHESTRATION.SYSTEM_PROMPT` — hardcoded constant in `@nexusai/shared` (last resort)
|
||||
|
||||
4. **Recent episode retrieval** — fetch the most recent episodes for the
|
||||
session (`recentEpisodeLimit`, default 5).
|
||||
|
||||
4. **Semantic search** — embed the user message, query Qdrant for the top
|
||||
5. **Semantic search** — embed the user message, query Qdrant for the top
|
||||
most similar past episodes (`semanticLimit`, `scoreThreshold`). Deduplicated
|
||||
against recent episodes. Non-critical — if it fails, pipeline continues with
|
||||
recency-only context.
|
||||
|
||||
5. **Entity search** — reuse the embedded user message vector to query the
|
||||
`entities` Qdrant collection (score threshold 0.6, limit 5). Returns
|
||||
entity payloads (`name`, `type`, `notes`) directly — no SQLite roundtrip
|
||||
needed. Non-critical — if it fails, pipeline continues without entity context.
|
||||
6. **Entity search** — query the `entities` Qdrant collection filtered by
|
||||
`projectId`. Non-project sessions receive no entity context. Non-critical.
|
||||
|
||||
6. **Prompt assembly** — combine system prompt, entity context, semantic
|
||||
episodes, recent episodes, and user message.
|
||||
7. **Prompt assembly** — combine resolved system prompt, entity context,
|
||||
semantic episodes, recent episodes, and user message.
|
||||
|
||||
7. **Inference** — send to inference service with settings-derived parameters
|
||||
8. **Inference** — send to inference service with settings-derived parameters
|
||||
(temperature, topP, topK, repeatPenalty). `/chat` awaits full response;
|
||||
`/chat/stream` pipes SSE chunks to the client.
|
||||
|
||||
8. **Episode write** — write the exchange back to memory. Fire-and-forget
|
||||
for `/chat`; awaited for `/chat/stream` to ensure the full text is
|
||||
accumulated before saving.
|
||||
9. **Episode write** — write the exchange back to memory with `projectId`.
|
||||
Fire-and-forget for `/chat`; awaited for `/chat/stream`.
|
||||
|
||||
9. **Auto-naming** — on `isFirstMessage && !session.name`, fire a secondary
|
||||
inference call with a naming prompt (max 20 tokens, temperature 0.3) and
|
||||
write the result back as `session.name`. Fully fire-and-forget.
|
||||
10. **Auto-naming** — on `isFirstMessage && !session.name`, fire a secondary
|
||||
inference call with a naming prompt (max 20 tokens, temperature 0.3) and
|
||||
write the result back as `session.name`. Fully fire-and-forget.
|
||||
|
||||
### Prompt Structure
|
||||
|
||||
```
|
||||
[System prompt]
|
||||
[Resolved system prompt]
|
||||
|
||||
Here is what you know about entities relevant to this conversation:
|
||||
- {name} ({type}): {notes}
|
||||
@@ -175,9 +178,9 @@ is terminated by `res.end()` after the done event.
|
||||
folder for richer metadata (label, description). Returns file size in GB.
|
||||
|
||||
`GET /models/props` fetches directly from llama-server via `LLAMA_SERVER_URL`.
|
||||
Returns `{ contextWindow, modelAlias }`. Used by the client to display
|
||||
read-only context window size and the currently loaded model in the settings
|
||||
panel. Returns `503` if llama-server is unreachable.
|
||||
Returns `{ contextWindow, modelAlias }`. `n_ctx` is at
|
||||
`data.default_generation_settings.n_ctx` in the llama-server response.
|
||||
Returns `503` if llama-server is unreachable.
|
||||
|
||||
## Sessions Route Behaviour
|
||||
|
||||
|
||||
Reference in New Issue
Block a user