feat(chat): view context — tell the assistant what you're looking at
Nightly Build / build (push) Successful in 7m51s

An eye next to the paperclip shares what the user has open with their next
message: the page, the folder being browsed, the file open in the viewer and
any highlighted passage (line numbers where a source view exists), plus which
entity a detail page is about. The bag is client-authored {label, value} pairs
in English — the backend only clamps (chars, never bytes), neutralizes the
harness tag and renders one <system-extra> block per message, deduped
consecutively so it appears exactly when the view changed. On by default,
per-device toggle, hover/tap to preview, a chip on every sent message;
docs/view-context.md for users, an updated harness.md clause for the model.
This commit is contained in:
Daniele
2026-08-23 20:53:30 +01:00
parent 488c702517
commit 505f2e95c1
42 changed files with 2096 additions and 122 deletions
+20
View File
@@ -2,6 +2,7 @@ import { html, nothing } from 'lit';
import { unsafeHTML } from 'lit/directives/unsafe-html.js';
import { LightElement } from '../lib/base.js';
import { t } from '../lib/i18n.js';
import { setSlice, clearSlice } from '../lib/view-context.js';
import { connectorIconUrl } from './shared/connector-common.js';
// Users admin — the list at `#users`, one user's page at `#users/{id}`.
@@ -29,6 +30,12 @@ function avatarColor(name) {
return `hsl(${h % 360}, 55%, 52%)`;
}
// The view-context slice this page owns (see `lib/view-context.js`). Qualified
// per page, like every `entity` contributor: these pages stay in the DOM while
// hidden, and a shared key would let a page that hides *after* another one
// shows wipe the fresh slice (listener order on `llm-page-change`).
const VIEW_SLICE = 'entity@users';
export class UsersPage extends LightElement {
static get properties() {
@@ -84,6 +91,15 @@ export class UsersPage extends LightElement {
this._connSaved = false;
this._plugSaved = false;
this._trgSaved = false;
clearSlice(VIEW_SLICE);
}
// The entity slice: which person's page is open — the username, and never a
// profile field (a detail page says *which* object, not what it contains).
_publishViewContext() {
const u = this._user;
setSlice(VIEW_SLICE,
this._view === 'user' && u ? [{ label: 'Open user', value: u.username }] : null);
}
connectedCallback() {
@@ -94,6 +110,7 @@ export class UsersPage extends LightElement {
this._open = e.detail.page === 'users';
this.style.display = this._open ? 'flex' : 'none';
if (this._open) { this._syncViewFromHash(); this._load(); }
else clearSlice(VIEW_SLICE);
});
window.addEventListener('hashchange', () => {
if (this._open) this._syncViewFromHash();
@@ -102,6 +119,7 @@ export class UsersPage extends LightElement {
disconnectedCallback() {
window.removeEventListener('locale-changed', this.__onLocaleChanged);
clearSlice(VIEW_SLICE);
super.disconnectedCallback();
}
@@ -117,6 +135,7 @@ export class UsersPage extends LightElement {
this._users = await uRes.json();
this._roles = await rRes.json();
if (this._view === 'user') this._enterDetail();
this._publishViewContext();
} catch (e) {
this._error = e.message;
}
@@ -137,6 +156,7 @@ export class UsersPage extends LightElement {
this._userId = id;
if (this._users) this._enterDetail();
}
this._publishViewContext();
}
get _user() { return (this._users ?? []).find(u => u.id === this._userId) ?? null; }