feat(chat): view context — tell the assistant what you're looking at
Nightly Build / build (push) Successful in 7m51s
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:
@@ -1,8 +1,14 @@
|
||||
import { html, nothing } from 'lit';
|
||||
import { LightElement } from '../../lib/base.js';
|
||||
import { t } from '../../lib/i18n.js';
|
||||
import { setSlice, clearSlice } from '../../lib/view-context.js';
|
||||
import '../shared/file-explorer.js';
|
||||
|
||||
/// The view-context slice this board owns (see `lib/view-context.js`). The
|
||||
/// board element is dropped by the host whenever the page closes or returns to
|
||||
/// the list, so `disconnectedCallback` is the whole cleanup story.
|
||||
const VIEW_SLICE = 'entity@projects';
|
||||
|
||||
/// A project's detail page: header + description, then two tabs — **Files** (the
|
||||
/// shared `<file-explorer>`, pointed at the project folder) and **Sharing**
|
||||
/// (member picker with read/write, mirroring the shared-folders UI).
|
||||
@@ -33,9 +39,23 @@ export class ProjectBoardSection extends LightElement {
|
||||
|
||||
disconnectedCallback() {
|
||||
window.removeEventListener('locale-changed', this.__onLocaleChanged);
|
||||
clearSlice(VIEW_SLICE);
|
||||
super.disconnectedCallback();
|
||||
}
|
||||
|
||||
// Say which project this is, and which tab is showing. The tab matters because
|
||||
// the explorer keeps its `path` slice standing while hidden behind *Sharing*:
|
||||
// without this, the context would describe a folder and not that the user is
|
||||
// looking at the member list.
|
||||
_publishViewContext() {
|
||||
const p = this._project;
|
||||
if (!p) return;
|
||||
setSlice(VIEW_SLICE, [
|
||||
{ label: 'Open project', value: p.root_path ? `${p.name} — folder ${p.root_path}` : String(p.name) },
|
||||
{ label: 'Open tab', value: this._tab === 'sharing' ? 'Sharing' : 'Files' },
|
||||
]);
|
||||
}
|
||||
|
||||
async load(projectId, tab) {
|
||||
this._projectId = projectId;
|
||||
this._project = null;
|
||||
@@ -49,6 +69,7 @@ export class ProjectBoardSection extends LightElement {
|
||||
if (!projRes.ok) throw new Error(`HTTP ${projRes.status}`);
|
||||
this._project = await projRes.json();
|
||||
if (usersRes.ok) this._users = await usersRes.json();
|
||||
this._publishViewContext();
|
||||
} catch (e) {
|
||||
this._error = e.message;
|
||||
}
|
||||
@@ -57,7 +78,10 @@ export class ProjectBoardSection extends LightElement {
|
||||
async _reload() {
|
||||
try {
|
||||
const res = await fetch(`/api/projects/${this._projectId}`);
|
||||
if (res.ok) this._project = await res.json();
|
||||
if (res.ok) {
|
||||
this._project = await res.json();
|
||||
this._publishViewContext();
|
||||
}
|
||||
} catch { /* transient */ }
|
||||
}
|
||||
|
||||
@@ -121,11 +145,13 @@ export class ProjectBoardSection extends LightElement {
|
||||
// Switch the visible tab without reloading (host back/forward sync).
|
||||
setTab(tab) {
|
||||
this._tab = tab === 'sharing' ? 'sharing' : 'files';
|
||||
this._publishViewContext();
|
||||
}
|
||||
|
||||
_selectTab(tab) {
|
||||
if (tab === this._tab) return;
|
||||
this._tab = tab;
|
||||
this._publishViewContext();
|
||||
this.dispatchEvent(new CustomEvent('project-tab-change', {
|
||||
detail: { tab }, bubbles: true, composed: true,
|
||||
}));
|
||||
|
||||
Reference in New Issue
Block a user