documentation update
This commit is contained in:
@@ -32,8 +32,7 @@ here for reference and direct debugging use.
|
|||||||
```
|
```
|
||||||
`model` and `temperature` are optional. Inference parameters (temperature,
|
`model` and `temperature` are optional. Inference parameters (temperature,
|
||||||
topP, topK, repeatPenalty) are read from `settings.json` on every request —
|
topP, topK, repeatPenalty) are read from `settings.json` on every request —
|
||||||
the request body values are not used for these; they are controlled via
|
controlled via `PATCH /settings`.
|
||||||
`PATCH /settings`.
|
|
||||||
|
|
||||||
**POST /chat — response:**
|
**POST /chat — response:**
|
||||||
```json
|
```json
|
||||||
@@ -91,7 +90,7 @@ Returns `{ sessionId, episodes: [...] }`. Episodes ordered newest first.
|
|||||||
|---|---|---|
|
|---|---|---|
|
||||||
| GET | /projects | Get all projects |
|
| GET | /projects | Get all projects |
|
||||||
| POST | /projects | Create a new project |
|
| POST | /projects | Create a new project |
|
||||||
| PATCH | /projects/:id | Update a project |
|
| PATCH | /projects/:id | Update a project (partial — any subset of fields) |
|
||||||
| DELETE | /projects/:id | Delete a project (nulls session assignments) |
|
| DELETE | /projects/:id | Delete a project (nulls session assignments) |
|
||||||
|
|
||||||
**POST /projects — body:**
|
**POST /projects — body:**
|
||||||
@@ -101,13 +100,27 @@ Returns `{ sessionId, episodes: [...] }`. Episodes ordered newest first.
|
|||||||
"description": "Optional description",
|
"description": "Optional description",
|
||||||
"colour": "#3d3a79",
|
"colour": "#3d3a79",
|
||||||
"icon": null,
|
"icon": null,
|
||||||
"isolated": 0
|
"isolated": 1
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
`name` is required. All other fields optional. `isolated` is `0` or `1`.
|
`name` is required. All other fields optional. `isolated` is always `1` —
|
||||||
Returns `201` with the created project object.
|
all projects use isolated memory. Returns `201` with the created project object.
|
||||||
|
|
||||||
**PATCH /projects/:id — body:** same fields as POST, all optional.
|
**PATCH /projects/:id — body:** any subset of fields, all optional.
|
||||||
|
|
||||||
|
| Field | Type | Description |
|
||||||
|
|---|---|---|
|
||||||
|
| `name` | string | Project name |
|
||||||
|
| `description` | string | Project description |
|
||||||
|
| `colour` | string | Hex colour for UI accent |
|
||||||
|
| `icon` | string | Icon identifier |
|
||||||
|
| `isolated` | integer | Memory isolation flag (always 1) |
|
||||||
|
| `notes` | string | User-authored project notes |
|
||||||
|
|
||||||
|
Only provided fields are updated — omitted fields are not touched. This
|
||||||
|
enables safe partial updates (e.g. saving just `notes` without affecting
|
||||||
|
`name` or `colour`). Both orchestration and memory service implement dynamic
|
||||||
|
field patching.
|
||||||
|
|
||||||
### Models
|
### Models
|
||||||
|
|
||||||
@@ -127,8 +140,9 @@ with `models.json` in the same folder for label and description metadata.
|
|||||||
```json
|
```json
|
||||||
{ "contextWindow": 64000, "modelAlias": "gemma-4-26B-A4B-Claude-Distill-APEX-I-Mini.gguf" }
|
{ "contextWindow": 64000, "modelAlias": "gemma-4-26B-A4B-Claude-Distill-APEX-I-Mini.gguf" }
|
||||||
```
|
```
|
||||||
Fetches directly from llama-server `/props`. Returns `503` if llama-server
|
Fetches directly from llama-server `/props`. `n_ctx` is at
|
||||||
is unreachable.
|
`data.default_generation_settings.n_ctx` in the llama-server response.
|
||||||
|
Returns `503` if llama-server is unreachable.
|
||||||
|
|
||||||
### Settings
|
### Settings
|
||||||
|
|
||||||
@@ -218,8 +232,7 @@ orchestration.
|
|||||||
```json
|
```json
|
||||||
{ "name": "Session Name", "projectId": 3 }
|
{ "name": "Session Name", "projectId": 3 }
|
||||||
```
|
```
|
||||||
Both fields are optional. Only provided fields are updated — other fields
|
Both fields are optional. Only provided fields are updated.
|
||||||
are not touched.
|
|
||||||
|
|
||||||
### Episodes
|
### Episodes
|
||||||
|
|
||||||
@@ -251,7 +264,7 @@ are not touched.
|
|||||||
| POST | /projects | Create a new project |
|
| POST | /projects | Create a new project |
|
||||||
| GET | /projects | Get all projects |
|
| GET | /projects | Get all projects |
|
||||||
| GET | /projects/:id | Get project by ID |
|
| GET | /projects/:id | Get project by ID |
|
||||||
| PATCH | /projects/:id | Update a project |
|
| PATCH | /projects/:id | Update a project (dynamic — any subset of fields) |
|
||||||
| DELETE | /projects/:id | Delete project + null session assignments |
|
| DELETE | /projects/:id | Delete project + null session assignments |
|
||||||
|
|
||||||
Same request/response shape as orchestration `/projects` above.
|
Same request/response shape as orchestration `/projects` above.
|
||||||
|
|||||||
@@ -1,15 +1,16 @@
|
|||||||
# Memory Isolation
|
# Memory Isolation
|
||||||
|
|
||||||
NexusAI implements project-scoped memory — sessions belonging to the same
|
NexusAI implements project-scoped memory — sessions belonging to the same
|
||||||
project can share semantic context, and isolated projects can be restricted
|
project share semantic context within that project's boundary. All projects
|
||||||
from drawing on memory outside the project. This document describes how the
|
are isolated by default.
|
||||||
system works end-to-end.
|
|
||||||
|
|
||||||
## Concepts
|
## Concepts
|
||||||
|
|
||||||
**Session** — a single conversation thread. Identified by `external_id`.
|
**Session** — a single conversation thread. Identified by `external_id`.
|
||||||
|
|
||||||
**Project** — a named grouping of sessions. Has an `isolated` flag (0 or 1).
|
**Project** — a named grouping of sessions. `isolated` is always `1` —
|
||||||
|
the toggle has been removed from the UI and `isolated: 1` is hardcoded on
|
||||||
|
project creation.
|
||||||
|
|
||||||
**Semantic search** — at inference time, the user's message is embedded and
|
**Semantic search** — at inference time, the user's message is embedded and
|
||||||
compared against past episodes in Qdrant to surface relevant context. The
|
compared against past episodes in Qdrant to surface relevant context. The
|
||||||
@@ -20,11 +21,10 @@ scope of this search is controlled by the project context.
|
|||||||
| Session state | Semantic search scope |
|
| Session state | Semantic search scope |
|
||||||
|---|---|
|
|---|---|
|
||||||
| No project | Own session's episodes only |
|
| No project | Own session's episodes only |
|
||||||
| Assigned to a non-isolated project | All episodes across all sessions in the project |
|
| Assigned to a project | All episodes across all sessions in that project |
|
||||||
| Assigned to an isolated project | All episodes within the project only |
|
|
||||||
| Removed from a project | Own session's episodes only (from that point) |
|
| Removed from a project | Own session's episodes only (from that point) |
|
||||||
|
|
||||||
Sessions with no project assigned behave the same as they always have —
|
Sessions with no project assigned behave as they always have —
|
||||||
only their own past episodes are searched.
|
only their own past episodes are searched.
|
||||||
|
|
||||||
## How It Works
|
## How It Works
|
||||||
@@ -44,16 +44,9 @@ if (session.project_id) {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
If the session belongs to any project (isolated or not), `projectSessionIds`
|
If the session belongs to any project, `projectSessionIds` is populated with
|
||||||
is populated with the internal integer IDs of all sessions in that project.
|
the internal integer IDs of all sessions in that project — creating a shared
|
||||||
|
memory pool across all conversations in the project.
|
||||||
For **non-isolated projects**, this expands the search to all project sessions.
|
|
||||||
For **isolated projects**, the same set is used but the intent is restriction
|
|
||||||
— since `projectSessionIds` only contains project sessions, no external
|
|
||||||
episodes can appear.
|
|
||||||
|
|
||||||
Both cases use the same code path — the `isolated` flag does not change the
|
|
||||||
query logic, only the conceptual meaning.
|
|
||||||
|
|
||||||
### Step 2 — Qdrant filter construction
|
### Step 2 — Qdrant filter construction
|
||||||
|
|
||||||
@@ -96,26 +89,11 @@ message, `getProjectSessions` will not include that session's ID, so its
|
|||||||
episodes disappear from the semantic search scope.
|
episodes disappear from the semantic search scope.
|
||||||
|
|
||||||
**New sessions created from ProjectView are assigned after the first message.**
|
**New sessions created from ProjectView are assigned after the first message.**
|
||||||
The `useChat` hook writes the `project_id` assignment via `updateSession` after
|
`handleNewProjectChat` in `App.jsx` calls `sendMessage` with the project ID,
|
||||||
`onDone` fires. There is a brief window during the first message where the
|
which is passed to `useChat`. After `onDone` fires, `useChat` calls
|
||||||
session has no project assigned. The project is correctly applied from the
|
`updateSession` to write the project assignment to the backend. There is a
|
||||||
second message onward.
|
brief window during the first message where the session has no project assigned.
|
||||||
|
The project is correctly applied from the second message onward.
|
||||||
## Isolated vs Non-Isolated
|
|
||||||
|
|
||||||
The `isolated` flag is stored on the project but does not currently change the
|
|
||||||
query logic — both isolated and non-isolated projects result in a
|
|
||||||
`projectSessionIds` filter. The distinction is semantic and enforced by
|
|
||||||
the project's membership:
|
|
||||||
|
|
||||||
- **Non-isolated** — intentionally draws from all sessions in the project,
|
|
||||||
creating a shared memory pool for related conversations
|
|
||||||
- **Isolated** — by design contains only sessions explicitly added to it,
|
|
||||||
so the same filter naturally restricts context to project-only episodes
|
|
||||||
|
|
||||||
If cross-project contamination became a concern (e.g. a session accidentally
|
|
||||||
added to the wrong project), removing it from the project immediately restores
|
|
||||||
isolation.
|
|
||||||
|
|
||||||
## Qdrant Payload Structure
|
## Qdrant Payload Structure
|
||||||
|
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ src/
|
|||||||
├── api/
|
├── api/
|
||||||
│ └── orchestration.js # All fetch calls to the orchestration service
|
│ └── orchestration.js # All fetch calls to the orchestration service
|
||||||
├── config/
|
├── config/
|
||||||
│ └── constants.js # FALLBACK_MODELS, DEFAULT_MODEL, API_DEFAULTS
|
│ └── constants.js # FALLBACK_MODELS, DEFAULT_MODEL, API_DEFAULTS, CLIENT_DEFAULTS
|
||||||
├── hooks/
|
├── hooks/
|
||||||
│ ├── useSession.js # Session list, history loading, active session state
|
│ ├── useSession.js # Session list, history loading, active session state
|
||||||
│ ├── useChat.js # Message sending, SSE streaming, message state
|
│ ├── useChat.js # Message sending, SSE streaming, message state
|
||||||
@@ -93,14 +93,16 @@ src/
|
|||||||
├── components/
|
├── components/
|
||||||
│ ├── App.jsx # Root component — layout, shared state, view routing
|
│ ├── App.jsx # Root component — layout, shared state, view routing
|
||||||
│ ├── Sidebar.jsx # Left sidebar — projects, recent chats, navigation
|
│ ├── Sidebar.jsx # Left sidebar — projects, recent chats, navigation
|
||||||
|
│ ├── HomeView.jsx # Landing screen — greeting, centred input, quick actions
|
||||||
│ ├── ChatWindow.jsx # Centre panel — message thread and input bar
|
│ ├── ChatWindow.jsx # Centre panel — message thread and input bar
|
||||||
│ ├── MessageBubble.jsx # Individual message bubble — renders markdown via react-markdown
|
│ ├── MessageBubble.jsx # Individual message bubble — renders markdown via react-markdown
|
||||||
│ ├── InfoPanel.jsx # Right panel — model selector and session metadata (slide-in)
|
│ ├── InfoPanel.jsx # Right panel — model selector and session metadata (slide-in)
|
||||||
│ ├── SessionModal.jsx # Modal for session rename, project assignment, delete
|
│ ├── SessionModal.jsx # Modal for session rename, project assignment, delete
|
||||||
│ ├── ProjectModal.jsx # Modal for project create, edit, delete
|
│ ├── ProjectModal.jsx # Modal for project create, edit, delete
|
||||||
│ ├── AllChatsView.jsx # Full paginated session list with multi-select bulk delete
|
│ ├── AllChatsView.jsx # Full paginated session list with multi-select bulk delete
|
||||||
│ ├── AllProjectsView.jsx # Project tile grid with create/edit/delete
|
│ ├── AllProjectsView.jsx # Project tile grid with create/edit/delete; tile click navigates to ProjectView
|
||||||
│ ├── ProjectView.jsx # Individual project — session list, new chat button
|
│ ├── ProjectView.jsx # Individual project — conversations, new chat input, memory
|
||||||
|
│ │ # placeholder, user notes, ⋮ edit/delete menu
|
||||||
│ ├── MemoryView.jsx # Paginated, searchable, expandable, deletable episode viewer
|
│ ├── MemoryView.jsx # Paginated, searchable, expandable, deletable episode viewer
|
||||||
│ └── SettingsView.jsx # Settings — Memory limits, Models (inference params, active
|
│ └── SettingsView.jsx # Settings — Memory limits, Models (inference params, active
|
||||||
│ # model, context window), Service Health, Appearance placeholder
|
│ # model, context window), Service Health, Appearance placeholder
|
||||||
@@ -108,8 +110,6 @@ src/
|
|||||||
└── main.jsx # React entry point
|
└── main.jsx # React entry point
|
||||||
```
|
```
|
||||||
|
|
||||||
> `SessionList.jsx` is superseded by `Sidebar.jsx` and kept only as a reference.
|
|
||||||
|
|
||||||
## Layout
|
## Layout
|
||||||
|
|
||||||
The app uses a view-based layout. `App.jsx` manages a `view` state string
|
The app uses a view-based layout. `App.jsx` manages a `view` state string
|
||||||
@@ -120,13 +120,13 @@ panel are persistent across all views.
|
|||||||
┌──────────────────┬──────────────────────────────┐
|
┌──────────────────┬──────────────────────────────┐
|
||||||
│ Sidebar │ Main Area (view-dependent) │
|
│ Sidebar │ Main Area (view-dependent) │
|
||||||
│ (collapsible) │ │
|
│ (collapsible) │ │
|
||||||
│ │ chat → ChatWindow │
|
│ │ home → HomeView │
|
||||||
│ + New Chat │ all-chats → AllChatsView │
|
│ + New Chat │ chat → ChatWindow │
|
||||||
│ ⊞ View Projects │ all-projects → AllProjectsView│
|
│ ⊞ View Projects │ all-chats → AllChatsView │
|
||||||
│ │ project → ProjectView │
|
│ │ all-projects → AllProjectsView│
|
||||||
│ PROJECTS ▾ │ settings → SettingsView │
|
│ PROJECTS ▾ │ project → ProjectView │
|
||||||
│ [tile] [tile] │ memory → MemoryView │
|
│ [tile] [tile] │ settings → SettingsView │
|
||||||
│ All Projects → │ │
|
│ All Projects → │ memory → MemoryView │
|
||||||
│ │ │
|
│ │ │
|
||||||
│ RECENT CHATS ▾ │ │
|
│ RECENT CHATS ▾ │ │
|
||||||
│ Session 1 │ │
|
│ Session 1 │ │
|
||||||
@@ -137,24 +137,43 @@ panel are persistent across all views.
|
|||||||
└──────────────────┴──────────────────────────────┘
|
└──────────────────┴──────────────────────────────┘
|
||||||
```
|
```
|
||||||
|
|
||||||
The sidebar collapses to a 48px icon rail. The right `InfoPanel` slides in
|
The sidebar collapses to a 48px icon rail and starts collapsed on the home
|
||||||
from the right using `transform: translateX()` — hidden by default, toggled
|
view. The right `InfoPanel` slides in from the right using
|
||||||
via the `⊹` button in the `ChatWindow` header.
|
`transform: translateX()` — hidden by default, toggled via the `⊹` button
|
||||||
|
in the `ChatWindow` header.
|
||||||
|
|
||||||
## View Routing
|
## View Routing
|
||||||
|
|
||||||
| View | Component | Trigger |
|
| View | Component | Trigger |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| `'chat'` | `ChatWindow` | Default; selecting a session; new chat |
|
| `'home'` | `HomeView` | Initial load; going back from chat with no history |
|
||||||
|
| `'chat'` | `ChatWindow` | Selecting a session; new chat; sending from HomeView |
|
||||||
| `'all-chats'` | `AllChatsView` | "All Chats →" or ☰ icon in collapsed rail |
|
| `'all-chats'` | `AllChatsView` | "All Chats →" or ☰ icon in collapsed rail |
|
||||||
| `'all-projects'` | `AllProjectsView` | "View Projects" button or ⊞ icon |
|
| `'all-projects'` | `AllProjectsView` | "View Projects" button or ⊞ icon |
|
||||||
| `'project'` | `ProjectView` | Clicking a project tile in the sidebar |
|
| `'project'` | `ProjectView` | Clicking a project tile in sidebar or AllProjectsView |
|
||||||
| `'settings'` | `SettingsView` | Settings button or ⚙ icon |
|
| `'settings'` | `SettingsView` | Settings button or ⚙ icon |
|
||||||
| `'memory'` | `MemoryView` | "Open →" button in Settings → Memory section |
|
| `'memory'` | `MemoryView` | "Open →" button in Settings → Memory section |
|
||||||
|
|
||||||
`activeProject` state in `App.jsx` tracks which project `ProjectView` is
|
`activeProject` state in `App.jsx` tracks which project `ProjectView` is
|
||||||
displaying. Set via `onSelectProject` before navigating to `'project'`.
|
displaying. Set via `onSelectProject` before navigating to `'project'`.
|
||||||
|
|
||||||
|
### View History Stack
|
||||||
|
|
||||||
|
`App.jsx` maintains a `viewHistory` array. Each `navigate(view)` call pushes
|
||||||
|
the current view onto the stack. `goBack()` pops the last entry and restores
|
||||||
|
it. All view components receive `onBack={goBack}` — no component hardcodes
|
||||||
|
its own back destination. Navigating to `'home'` collapses the sidebar;
|
||||||
|
leaving `'home'` expands it.
|
||||||
|
|
||||||
|
## Home View
|
||||||
|
|
||||||
|
`HomeView` is the landing screen shown on initial load and when there are no
|
||||||
|
active sessions. It displays:
|
||||||
|
- Time-based greeting ("Morning / Afternoon / Evening, Tim")
|
||||||
|
- Currently loaded model name (from `modelProps.modelAlias`, stripped of `.gguf`)
|
||||||
|
- Centred textarea input — sending creates a new session and navigates to chat
|
||||||
|
- Quick action pills that populate the input without auto-sending
|
||||||
|
|
||||||
## CSS Architecture
|
## CSS Architecture
|
||||||
|
|
||||||
Styles follow a hybrid approach — CSS utility classes for static reusable
|
Styles follow a hybrid approach — CSS utility classes for static reusable
|
||||||
@@ -217,6 +236,10 @@ the `uuid` package. New sessions are created locally and auto-registered in
|
|||||||
the memory service on the first message. The session list refreshes after
|
the memory service on the first message. The session list refreshes after
|
||||||
each completed response to surface newly created sessions.
|
each completed response to surface newly created sessions.
|
||||||
|
|
||||||
|
`useSession.selectSession` skips the history fetch for new (`isNew: true`)
|
||||||
|
sessions — fetching history for an unsaved session would 404 since it doesn't
|
||||||
|
exist in the backend yet.
|
||||||
|
|
||||||
### Auto-naming
|
### Auto-naming
|
||||||
|
|
||||||
After the first exchange completes, orchestration fires a secondary inference
|
After the first exchange completes, orchestration fires a secondary inference
|
||||||
@@ -236,46 +259,60 @@ Session rows support rename, project assignment, and delete via:
|
|||||||
`SessionModal` handles rename and project assignment together in `settings`
|
`SessionModal` handles rename and project assignment together in `settings`
|
||||||
mode, and delete confirmation in `confirm-delete` mode.
|
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:
|
|
||||||
|
|
||||||
```js
|
|
||||||
function handleSessionsChange(deletedSession) {
|
|
||||||
if (deletedSession?.external_id === activeSession?.external_id) {
|
|
||||||
selectSession(null);
|
|
||||||
}
|
|
||||||
refreshSessions();
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Key Patterns
|
### Key Patterns
|
||||||
|
|
||||||
- Button nesting: action icons are siblings of row buttons, not children — HTML forbids `<button>` inside `<button>`
|
- 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
|
- Context menu rendered outside sidebar via React fragment to avoid `overflow: hidden` clipping
|
||||||
- `useContextMenu` dismisses on a `window` click listener
|
- `useContextMenu` dismisses on a `window` click listener
|
||||||
- Dynamic `updateSession` SQL builds `SET` clause from only the fields passed — prevents accidental overwrites
|
- 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)
|
||||||
|
|
||||||
## Project Management
|
## Project Management
|
||||||
|
|
||||||
|
All projects are isolated by default (`isolated: 1` hardcoded on create).
|
||||||
|
The isolated toggle has been removed from `ProjectModal`.
|
||||||
|
|
||||||
`useProjects` fetches the project list from `GET /projects` on mount and
|
`useProjects` fetches the project list from `GET /projects` on mount and
|
||||||
exposes `refreshProjects` for keeping the sidebar in sync after mutations.
|
exposes `refreshProjects` for keeping the sidebar in sync after mutations.
|
||||||
|
|
||||||
`ProjectModal` handles create, edit, and delete confirmation. Fields: name
|
`ProjectModal` handles create, edit, and delete confirmation. Fields: name
|
||||||
(required), description (optional), colour picker, isolated toggle.
|
(required), description (optional), colour picker.
|
||||||
|
|
||||||
`ProjectView` shows the project's name, description, isolated badge (if set),
|
Clicking a project tile in `AllProjectsView` calls `onSelectProject` then
|
||||||
and a filtered session list. The "+ New Chat" button creates a new session,
|
navigates to `'project'`.
|
||||||
navigates to `'chat'`, and writes the project assignment after the first message.
|
|
||||||
|
### ProjectView
|
||||||
|
|
||||||
|
`ProjectView` is a full project workspace with:
|
||||||
|
- Colour accent bar + project title + description
|
||||||
|
- ⋮ dropdown menu for edit (opens `ProjectModal` pre-filled) and delete
|
||||||
|
- Conversations list — each session is a clickable row navigating to `'chat'`
|
||||||
|
- `ChatInput` component below the list (or centred when no sessions exist) for
|
||||||
|
starting new project-tied conversations without a separate button
|
||||||
|
- **Project Memory** — placeholder section explaining upcoming auto-summary feature
|
||||||
|
- **Project Notes** — textarea with Save button; notes saved to `projects.notes`
|
||||||
|
column in SQLite; save button only appears when content has changed from last
|
||||||
|
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.
|
||||||
|
|
||||||
For memory isolation behaviour, see `memory-isolation.md`.
|
For memory isolation behaviour, see `memory-isolation.md`.
|
||||||
|
|
||||||
## Settings
|
## Settings
|
||||||
|
|
||||||
`useSettings` fetches from `GET /settings` on mount and exposes a `saveSetting(key, value)`
|
`useSettings` fetches from `GET /settings` on mount and exposes a
|
||||||
helper that issues a `PATCH /settings` with a single key-value pair. The `saving`
|
`saveSetting(key, value)` helper that issues a `PATCH /settings` with a
|
||||||
boolean is exposed for disabling save buttons during in-flight requests.
|
single key-value pair. The `saving` boolean is exposed for disabling save
|
||||||
|
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.
|
||||||
|
|
||||||
`SettingsView` is organised into sections:
|
`SettingsView` is organised into sections:
|
||||||
|
|
||||||
@@ -285,3 +322,7 @@ boolean is exposed for disabling save buttons during in-flight requests.
|
|||||||
loaded model from llama-server)
|
loaded model from llama-server)
|
||||||
- **About** — service health check panel, version
|
- **About** — service health check panel, version
|
||||||
- **Appearance** — theme (coming soon)
|
- **Appearance** — theme (coming soon)
|
||||||
|
|
||||||
|
An error boundary (`SettingsSectionErrorBoundary`) wraps the Models section —
|
||||||
|
if the models fetch fails, only that section shows an error with a Retry
|
||||||
|
button rather than blanking the entire settings view.
|
||||||
@@ -58,7 +58,7 @@ Six core tables:
|
|||||||
- **entities** — named things the system learns about (people, places, concepts)
|
- **entities** — named things the system learns about (people, places, concepts)
|
||||||
- **relationships** — directional labeled links between entities
|
- **relationships** — directional labeled links between entities
|
||||||
- **summaries** — condensed episode groups for efficient context retrieval
|
- **summaries** — condensed episode groups for efficient context retrieval
|
||||||
- **projects** — named groupings of sessions with `name`, `description`, `colour`, `icon`, `isolated`
|
- **projects** — named groupings of sessions with `name`, `description`, `colour`, `icon`, `isolated`, `notes`
|
||||||
|
|
||||||
### Migrations
|
### Migrations
|
||||||
|
|
||||||
@@ -70,6 +70,7 @@ try { db.exec(`ALTER TABLE sessions ADD COLUMN name TEXT`); } catch {}
|
|||||||
try { db.exec(`ALTER TABLE sessions ADD COLUMN project_id INTEGER REFERENCES projects(id)`); } catch {}
|
try { db.exec(`ALTER TABLE sessions ADD COLUMN project_id INTEGER REFERENCES projects(id)`); } catch {}
|
||||||
try { db.exec(`CREATE INDEX IF NOT EXISTS idx_sessions_project ON sessions(project_id)`); } catch {}
|
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 isolated INTEGER NOT NULL DEFAULT 0`); } catch {}
|
||||||
|
try { db.exec(`ALTER TABLE projects ADD COLUMN notes TEXT`); } catch {}
|
||||||
```
|
```
|
||||||
|
|
||||||
New migrations are always appended here — never modify the schema file for
|
New migrations are always appended here — never modify the schema file for
|
||||||
@@ -87,22 +88,34 @@ keep the FTS index automatically in sync with the episodes table.
|
|||||||
- `foreign_keys = ON` — enforces referential integrity and cascade deletes
|
- `foreign_keys = ON` — enforces referential integrity and cascade deletes
|
||||||
- PRAGMAs set via `db.pragma()`, not `db.exec()`
|
- PRAGMAs set via `db.pragma()`, not `db.exec()`
|
||||||
|
|
||||||
### Dynamic Session Updates
|
### Dynamic Updates
|
||||||
|
|
||||||
`updateSession` builds its `SET` clause dynamically from only the fields
|
Both `updateSession` and `updateProject` build their `SET` clause dynamically
|
||||||
passed — prevents partial updates from overwriting fields that weren't
|
from only the fields passed — prevents partial updates from overwriting fields
|
||||||
touched:
|
that weren't touched:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
function updateSession(id, { name, projectId } = {}) {
|
// updateProject example
|
||||||
|
function updateProject(id, fields = {}) {
|
||||||
|
const allowed = ['name', 'description', 'colour', 'icon', 'isolated', 'notes'];
|
||||||
const updates = [];
|
const updates = [];
|
||||||
const values = [];
|
const values = [];
|
||||||
if (name !== undefined) { updates.push('name = ?'); values.push(name ?? null); }
|
for (const key of allowed) {
|
||||||
if (projectId !== undefined) { updates.push('project_id = ?'); values.push(projectId ?? null); }
|
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);
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
This means saving just `{ notes: "..." }` won't touch `name`, `colour`, or
|
||||||
|
any other field.
|
||||||
|
|
||||||
## Qdrant / Semantic Layer
|
## Qdrant / Semantic Layer
|
||||||
|
|
||||||
Three Qdrant collections are initialized on service startup:
|
Three Qdrant collections are initialized on service startup:
|
||||||
|
|||||||
@@ -302,14 +302,16 @@ function ChatInput({ value, onChange, onSend, placeholder, autoFocus }) {
|
|||||||
|
|
||||||
function NotesSection({ projectId, initialNotes }) {
|
function NotesSection({ projectId, initialNotes }) {
|
||||||
const [notes, setNotes] = useState(initialNotes);
|
const [notes, setNotes] = useState(initialNotes);
|
||||||
|
const [savedNotes, setSavedNotes] = useState(initialNotes);
|
||||||
const [saving, setSaving] = useState(false);
|
const [saving, setSaving] = useState(false);
|
||||||
|
|
||||||
const isDirty = notes !== initialNotes;
|
const isDirty = notes !== savedNotes;
|
||||||
|
|
||||||
async function handleSave() {
|
async function handleSave() {
|
||||||
setSaving(true);
|
setSaving(true);
|
||||||
try {
|
try {
|
||||||
await updateProject(projectId, { notes });
|
await updateProject(projectId, { notes });
|
||||||
|
setSavedNotes(notes);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('[NotesSection] Save failed:', err.message);
|
console.error('[NotesSection] Save failed:', err.message);
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
Reference in New Issue
Block a user