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,5 +1,7 @@
|
||||
import { LitElement, html, nothing } from 'lit';
|
||||
import { t } from '../lib/i18n.js';
|
||||
import { claimRouteProvider, refreshRoute } from '../lib/view-context.js';
|
||||
import { mobileRouteSliceFor } from '../lib/view-context-routes.js';
|
||||
import { LoginPage } from './login-page.js';
|
||||
import { installSessionExpiryWatch } from '../lib/session-expiry.js';
|
||||
import { installSessionRelogin } from './session-relogin.js';
|
||||
@@ -65,6 +67,14 @@ class MobileApp extends LitElement {
|
||||
this._onHashChange = () => this._applyHash();
|
||||
window.addEventListener('hashchange', this._onHashChange);
|
||||
window.addEventListener('popstate', this._onHashChange);
|
||||
// This shell's sections are not desktop pages, so `pageFromHash` cannot
|
||||
// describe them: the route slice is rendered from here instead. The
|
||||
// provider re-reads the hash at every sync, which is what makes it immune
|
||||
// to listener ordering between this element and the store.
|
||||
claimRouteProvider(() => {
|
||||
const { section, projectId } = this._readHash();
|
||||
return mobileRouteSliceFor({ section, projectId, projectLabel: this._chatLabel || null });
|
||||
});
|
||||
// Default route when no hash is present (replaceState: no history entry).
|
||||
if (!location.hash) history.replaceState(null, '', '#chat');
|
||||
this._applyHash();
|
||||
@@ -74,6 +84,7 @@ class MobileApp extends LitElement {
|
||||
super.disconnectedCallback();
|
||||
window.removeEventListener('hashchange', this._onHashChange);
|
||||
window.removeEventListener('popstate', this._onHashChange);
|
||||
claimRouteProvider(null);
|
||||
}
|
||||
|
||||
// ── Hash routing ───────────────────────────────────────────────────────────
|
||||
@@ -133,6 +144,11 @@ class MobileApp extends LitElement {
|
||||
projectId ? 'project-' + projectId : null,
|
||||
section === 'file_viewer' ? filePath : null,
|
||||
);
|
||||
// Re-publish the route slice with the state just applied: the store's own
|
||||
// `hashchange` listener may have run before this one, and the provider only
|
||||
// reads current state, so a refresh here is what keeps the two independent
|
||||
// of listener order.
|
||||
refreshRoute();
|
||||
}
|
||||
|
||||
// Resolve the display label for a project id (shown in the chat header). Cached
|
||||
@@ -141,6 +157,7 @@ class MobileApp extends LitElement {
|
||||
async _resolveLabel(projectId) {
|
||||
if (this._projectLabels[projectId] != null) {
|
||||
this._chatLabel = this._projectLabels[projectId];
|
||||
refreshRoute();
|
||||
return;
|
||||
}
|
||||
try {
|
||||
@@ -149,6 +166,7 @@ class MobileApp extends LitElement {
|
||||
for (const p of await res.json()) this._projectLabels[p.id] = p.name;
|
||||
} catch { /* keep whatever label we have */ }
|
||||
this._chatLabel = this._projectLabels[projectId] ?? projectId;
|
||||
refreshRoute();
|
||||
}
|
||||
|
||||
_nav(section) {
|
||||
|
||||
Reference in New Issue
Block a user