honcho: plugin web pages with i18n, defer plugin detail to custom admin page
Nightly Build / build (push) Failing after 6m13s
Nightly Build / build (push) Failing after 6m13s
This commit is contained in:
@@ -0,0 +1,46 @@
|
||||
// Shared helpers for the Honcho page fragments.
|
||||
//
|
||||
// Served at `/api/plugin/honcho/web/common.js` and imported by the two page
|
||||
// fragments via a relative `./common.js` specifier. Everything the fragments
|
||||
// need is self-contained here — the host injects no APIs (see the
|
||||
// `Plugin::web_pages` contract): they talk only to `/api/plugin/honcho/…` and,
|
||||
// for save/opt-in, the host's core plugin endpoints `/api/plugins/…` (the
|
||||
// fragment runs with the logged-in user's full session privileges).
|
||||
//
|
||||
// i18n: the plugin ships its own dictionary (`./i18n.js`) and registers it into
|
||||
// the host's shared strings via `addStrings` (imported from the app root by the
|
||||
// absolute `/lib/i18n.js` specifier — the same module the host app uses, so
|
||||
// `t()` and `locale-changed` are shared). `HonchoBase` mixes in `I18nMixin` so
|
||||
// every fragment re-renders on a language switch. Register once, at module load.
|
||||
import { LitElement } from 'lit';
|
||||
import { t, addStrings, I18nMixin } from '/lib/i18n.js';
|
||||
import STRINGS from './i18n.js';
|
||||
|
||||
addStrings(STRINGS);
|
||||
|
||||
export { t };
|
||||
|
||||
/// JSON fetch that throws the server's error text on non-2xx and tolerates an
|
||||
/// empty (204) body. The server's error text is already localized (the backend
|
||||
/// resolves the caller's locale), so it is safe to surface directly.
|
||||
export async function jf(url, opts = {}) {
|
||||
const res = await fetch(url, {
|
||||
headers: { 'Content-Type': 'application/json', ...(opts.headers || {}) },
|
||||
...opts,
|
||||
});
|
||||
if (!res.ok) {
|
||||
const txt = await res.text().catch(() => '');
|
||||
throw new Error(txt || `HTTP ${res.status}`);
|
||||
}
|
||||
if (res.status === 204) return null;
|
||||
const ct = res.headers.get('content-type') || '';
|
||||
return ct.includes('application/json') ? res.json() : res.text();
|
||||
}
|
||||
|
||||
/// Base for the Honcho fragments: renders into light DOM (so Bootstrap classes
|
||||
/// and the app's theme CSS variables apply), re-renders on locale change, and
|
||||
/// exposes the plugin's API root from the host-set `plugin-id` attribute.
|
||||
export class HonchoBase extends I18nMixin(LitElement) {
|
||||
createRenderRoot() { return this; }
|
||||
get api() { return `/api/plugin/${this.getAttribute('plugin-id') || 'honcho'}`; }
|
||||
}
|
||||
@@ -0,0 +1,163 @@
|
||||
// Honcho admin config page (page_id `config`, admin_only).
|
||||
//
|
||||
// The plugin's dedicated admin surface, richer than the generic
|
||||
// `#plugin-detail` form: connection config + a "Test connection" check against
|
||||
// the *current draft* before saving. Persistence reuses the core plugin
|
||||
// endpoints — `GET /api/plugins` to read the row, `PUT /api/plugins/honcho` to
|
||||
// save `{enabled, config}` — so nothing is stored through this fragment's own
|
||||
// backend. Default-exports the element class; the host registers it.
|
||||
import { html, nothing } from 'lit';
|
||||
import { HonchoBase, jf, t } from './common.js';
|
||||
|
||||
const P = 'plugin.honcho';
|
||||
const ID = 'honcho';
|
||||
|
||||
export default class HonchoConfigPage extends HonchoBase {
|
||||
static get properties() {
|
||||
return {
|
||||
_plugin: { state: true }, // PluginInfo | null
|
||||
_draft: { state: true }, // { base_url, api_key, workspace_id }
|
||||
_status: { state: true }, // { ok?, err? } for save
|
||||
_test: { state: true }, // { busy?, ok?, err? } for the connection test
|
||||
_error: { state: true },
|
||||
_loading: { state: true },
|
||||
};
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this._plugin = null;
|
||||
this._draft = { base_url: '', api_key: '', workspace_id: '' };
|
||||
this._status = {};
|
||||
this._test = {};
|
||||
this._error = null;
|
||||
this._loading = true;
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
super.connectedCallback();
|
||||
this._load();
|
||||
}
|
||||
|
||||
async _load() {
|
||||
this._loading = true;
|
||||
this._error = null;
|
||||
try {
|
||||
const all = await jf('/api/plugins');
|
||||
const p = (all ?? []).find(x => x.id === ID) ?? null;
|
||||
if (!p) { this._error = t(`${P}.config.not_found`); this._plugin = null; return; }
|
||||
this._plugin = p;
|
||||
this._draft = {
|
||||
base_url: p.config?.base_url ?? '',
|
||||
api_key: p.config?.api_key ?? '',
|
||||
workspace_id: p.config?.workspace_id ?? '',
|
||||
};
|
||||
} catch (e) {
|
||||
this._error = e.message;
|
||||
} finally {
|
||||
this._loading = false;
|
||||
}
|
||||
}
|
||||
|
||||
_set(key, value) {
|
||||
this._draft = { ...this._draft, [key]: value };
|
||||
this._status = {};
|
||||
this._test = {};
|
||||
}
|
||||
|
||||
async _save(enabled) {
|
||||
this._status = {};
|
||||
if (!this._draft.base_url?.trim()) {
|
||||
this._status = { err: t(`${P}.config.required`) };
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await jf(`/api/plugins/${ID}`, {
|
||||
method: 'PUT',
|
||||
body: JSON.stringify({ enabled, config: this._draft }),
|
||||
});
|
||||
this._status = { ok: t(`${P}.config.saved`) };
|
||||
await this._load();
|
||||
window.dispatchEvent(new CustomEvent('plugins-changed'));
|
||||
} catch (e) {
|
||||
this._status = { err: e.message };
|
||||
}
|
||||
}
|
||||
|
||||
async _testConnection() {
|
||||
this._test = { busy: true };
|
||||
try {
|
||||
const r = await jf(`${this.api}/admin/test`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ base_url: this._draft.base_url, api_key: this._draft.api_key }),
|
||||
});
|
||||
this._test = { ok: t(`${P}.config.test_ok`, { n: r?.workspaces ?? 0 }) };
|
||||
} catch (e) {
|
||||
this._test = { err: e.message };
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const p = this._plugin;
|
||||
return html`
|
||||
<div class="um-page">
|
||||
<div class="um-header">
|
||||
<h2 class="um-title"><i class="bi bi-stars me-2"></i>${t(`${P}.config.title`)}</h2>
|
||||
</div>
|
||||
<div style="padding:0 1.25rem 2rem; max-width:640px; overflow:auto">
|
||||
${this._error ? html`<div class="alert alert-danger py-2" style="font-size:.85rem">${this._error}</div>` : nothing}
|
||||
${this._loading
|
||||
? html`<div class="um-empty"><i class="bi bi-hourglass-split"></i> ${t(`${P}.config.loading`)}</div>`
|
||||
: p ? this._renderForm(p) : nothing}
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
_renderForm(p) {
|
||||
const d = this._draft;
|
||||
return html`
|
||||
<p class="text-body-secondary" style="font-size:.9rem">${t(`${P}.config.intro`)}</p>
|
||||
|
||||
<div class="form-check form-switch mb-3">
|
||||
<input class="form-check-input" type="checkbox" role="switch" id="honcho-enabled"
|
||||
.checked=${!!p.enabled} @change=${(e) => this._save(e.target.checked)} />
|
||||
<label class="form-check-label" for="honcho-enabled" style="font-size:.85rem">${t(`${P}.config.enabled`)}</label>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label">${t(`${P}.config.base_url`)}<span class="text-danger">*</span></label>
|
||||
<input class="form-control" type="text" .value=${d.base_url}
|
||||
@input=${(e) => this._set('base_url', e.target.value)} />
|
||||
<div class="form-text" style="font-size:.72rem">${t(`${P}.config.base_url_hint`)}</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label">${t(`${P}.config.api_key`)}</label>
|
||||
<input class="form-control" type="password" autocomplete="off" .value=${d.api_key}
|
||||
@input=${(e) => this._set('api_key', e.target.value)} />
|
||||
<div class="form-text" style="font-size:.72rem">${t(`${P}.config.api_key_hint`)}</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label">${t(`${P}.config.workspace`)}</label>
|
||||
<input class="form-control" type="text" .value=${d.workspace_id}
|
||||
@input=${(e) => this._set('workspace_id', e.target.value)} />
|
||||
<div class="form-text" style="font-size:.72rem">${t(`${P}.config.workspace_hint`)}</div>
|
||||
</div>
|
||||
|
||||
${this._status.err ? html`<div class="alert alert-danger py-2 mb-3" style="font-size:.82rem">${this._status.err}</div>` : nothing}
|
||||
${this._status.ok ? html`<div class="alert alert-success py-2 mb-3" style="font-size:.82rem">${this._status.ok}</div>` : nothing}
|
||||
${this._test.err ? html`<div class="alert alert-danger py-2 mb-3" style="font-size:.82rem">${this._test.err}</div>` : nothing}
|
||||
${this._test.ok ? html`<div class="alert alert-success py-2 mb-3" style="font-size:.82rem"><i class="bi bi-check-circle me-1"></i>${this._test.ok}</div>` : nothing}
|
||||
|
||||
<div class="d-flex gap-2">
|
||||
<button class="btn btn-primary btn-sm" @click=${() => this._save(p.enabled)}>
|
||||
<i class="bi bi-check-lg me-1"></i>${t(`${P}.config.save`)}
|
||||
</button>
|
||||
<button class="btn btn-outline-secondary btn-sm" ?disabled=${this._test.busy}
|
||||
@click=${() => this._testConnection()}>
|
||||
<i class="bi bi-plug me-1"></i>${this._test.busy ? t(`${P}.config.testing`) : t(`${P}.config.test`)}
|
||||
</button>
|
||||
</div>`;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
// Frontend translations for the Honcho page fragments.
|
||||
//
|
||||
// Served at `/api/plugin/honcho/web/i18n.js` and imported by `common.js`, which
|
||||
// registers it into the host's shared dictionaries via `addStrings` (see
|
||||
// `web/lib/i18n.js`). Keys are namespaced `plugin.honcho.*` so they never
|
||||
// collide with core keys. These are the *frontend* UI strings; the plugin's
|
||||
// backend error strings live in `../i18n/*.json` and reach the browser already
|
||||
// translated as HTTP response text.
|
||||
const P = 'plugin.honcho';
|
||||
|
||||
export default {
|
||||
en: {
|
||||
// Admin config page
|
||||
[`${P}.config.title`]: 'Honcho — Long-term memory',
|
||||
[`${P}.config.intro`]: 'Connect the Honcho memory server. When enabled, each user can opt in from their own Long-term memory page; nothing leaves the box until they do.',
|
||||
[`${P}.config.enabled`]: 'Plugin enabled',
|
||||
[`${P}.config.base_url`]: 'Server URL',
|
||||
[`${P}.config.base_url_hint`]: 'e.g. http://localhost:8000',
|
||||
[`${P}.config.api_key`]: 'API key',
|
||||
[`${P}.config.api_key_hint`]: 'Leave empty for a local, unauthenticated instance.',
|
||||
[`${P}.config.workspace`]: 'Workspace ID',
|
||||
[`${P}.config.workspace_hint`]:'One shared workspace for the whole instance; each user is a separate peer inside it.',
|
||||
[`${P}.config.save`]: 'Save',
|
||||
[`${P}.config.saved`]: 'Saved.',
|
||||
[`${P}.config.test`]: 'Test connection',
|
||||
[`${P}.config.testing`]: 'Testing…',
|
||||
[`${P}.config.test_ok`]: 'Connected — {n} workspace(s) reachable.',
|
||||
[`${P}.config.required`]: 'The server URL is required.',
|
||||
[`${P}.config.loading`]: 'Loading…',
|
||||
[`${P}.config.not_found`]: 'Honcho plugin not found.',
|
||||
|
||||
// User opt-in page
|
||||
[`${P}.memory.title`]: 'Long-term memory',
|
||||
[`${P}.memory.intro`]: 'Let the assistant remember you across conversations, so it gets more helpful over time.',
|
||||
[`${P}.memory.privacy_title`]: 'Before you turn this on',
|
||||
[`${P}.memory.privacy_body`]: 'Your messages are stored in cleartext on the Honcho memory server, outside your encrypted database. Turn this on only if you are comfortable with that. It is off unless you enable it, and you can turn it off at any time.',
|
||||
[`${P}.memory.toggle`]: 'Remember me across conversations',
|
||||
[`${P}.memory.save`]: 'Save',
|
||||
[`${P}.memory.saved`]: 'Saved.',
|
||||
[`${P}.memory.loading`]: 'Loading…',
|
||||
[`${P}.memory.unavailable`]: 'Long-term memory is not available to you yet. Ask your administrator to grant access.',
|
||||
[`${P}.memory.soon_title`]: 'Coming soon',
|
||||
[`${P}.memory.soon_body`]: 'Soon you will be able to ask Honcho what it remembers about you, and manage it, right from this page.',
|
||||
},
|
||||
|
||||
it: {
|
||||
[`${P}.config.title`]: 'Honcho — Memoria a lungo termine',
|
||||
[`${P}.config.intro`]: 'Collega il server di memoria Honcho. Quando è attivo, ogni utente può dare il consenso dalla propria pagina Memoria a lungo termine; finché non lo fa, nulla lascia il box.',
|
||||
[`${P}.config.enabled`]: 'Plugin attivo',
|
||||
[`${P}.config.base_url`]: 'URL del server',
|
||||
[`${P}.config.base_url_hint`]: 'es. http://localhost:8000',
|
||||
[`${P}.config.api_key`]: 'Chiave API',
|
||||
[`${P}.config.api_key_hint`]: 'Lascia vuoto per un’istanza locale senza autenticazione.',
|
||||
[`${P}.config.workspace`]: 'ID workspace',
|
||||
[`${P}.config.workspace_hint`]:'Un solo workspace condiviso per l’intera istanza; ogni utente è un peer separato al suo interno.',
|
||||
[`${P}.config.save`]: 'Salva',
|
||||
[`${P}.config.saved`]: 'Salvato.',
|
||||
[`${P}.config.test`]: 'Prova connessione',
|
||||
[`${P}.config.testing`]: 'Verifica…',
|
||||
[`${P}.config.test_ok`]: 'Connesso — {n} workspace raggiungibili.',
|
||||
[`${P}.config.required`]: 'L’URL del server è obbligatorio.',
|
||||
[`${P}.config.loading`]: 'Caricamento…',
|
||||
[`${P}.config.not_found`]: 'Plugin Honcho non trovato.',
|
||||
|
||||
[`${P}.memory.title`]: 'Memoria a lungo termine',
|
||||
[`${P}.memory.intro`]: 'Permetti all’assistente di ricordarti tra una conversazione e l’altra, così diventa più utile nel tempo.',
|
||||
[`${P}.memory.privacy_title`]: 'Prima di attivarla',
|
||||
[`${P}.memory.privacy_body`]: 'I tuoi messaggi vengono memorizzati in chiaro sul server di memoria Honcho, fuori dal tuo database cifrato. Attivala solo se ti sta bene. È disattivata finché non la abiliti, e puoi disattivarla in qualsiasi momento.',
|
||||
[`${P}.memory.toggle`]: 'Ricordami tra le conversazioni',
|
||||
[`${P}.memory.save`]: 'Salva',
|
||||
[`${P}.memory.saved`]: 'Salvato.',
|
||||
[`${P}.memory.loading`]: 'Caricamento…',
|
||||
[`${P}.memory.unavailable`]: 'La memoria a lungo termine non è ancora disponibile per te. Chiedi all’amministratore di darti l’accesso.',
|
||||
[`${P}.memory.soon_title`]: 'In arrivo',
|
||||
[`${P}.memory.soon_body`]: 'Presto potrai chiedere a Honcho cosa ricorda di te e gestirlo, direttamente da questa pagina.',
|
||||
},
|
||||
|
||||
fr: {
|
||||
[`${P}.config.title`]: 'Honcho — Mémoire à long terme',
|
||||
[`${P}.config.intro`]: 'Connectez le serveur de mémoire Honcho. Une fois activé, chaque utilisateur peut consentir depuis sa page Mémoire à long terme ; rien ne quitte la machine tant qu’il ne l’a pas fait.',
|
||||
[`${P}.config.enabled`]: 'Plugin activé',
|
||||
[`${P}.config.base_url`]: 'URL du serveur',
|
||||
[`${P}.config.base_url_hint`]: 'ex. http://localhost:8000',
|
||||
[`${P}.config.api_key`]: 'Clé API',
|
||||
[`${P}.config.api_key_hint`]: 'Laissez vide pour une instance locale sans authentification.',
|
||||
[`${P}.config.workspace`]: 'ID de l’espace',
|
||||
[`${P}.config.workspace_hint`]:'Un seul espace partagé pour toute l’instance ; chaque utilisateur y est un peer distinct.',
|
||||
[`${P}.config.save`]: 'Enregistrer',
|
||||
[`${P}.config.saved`]: 'Enregistré.',
|
||||
[`${P}.config.test`]: 'Tester la connexion',
|
||||
[`${P}.config.testing`]: 'Test…',
|
||||
[`${P}.config.test_ok`]: 'Connecté — {n} espace(s) accessibles.',
|
||||
[`${P}.config.required`]: 'L’URL du serveur est obligatoire.',
|
||||
[`${P}.config.loading`]: 'Chargement…',
|
||||
[`${P}.config.not_found`]: 'Plugin Honcho introuvable.',
|
||||
|
||||
[`${P}.memory.title`]: 'Mémoire à long terme',
|
||||
[`${P}.memory.intro`]: 'Laissez l’assistant se souvenir de vous d’une conversation à l’autre, pour qu’il devienne plus utile avec le temps.',
|
||||
[`${P}.memory.privacy_title`]: 'Avant d’activer',
|
||||
[`${P}.memory.privacy_body`]: 'Vos messages sont stockés en clair sur le serveur de mémoire Honcho, en dehors de votre base chiffrée. N’activez que si cela vous convient. C’est désactivé tant que vous ne l’activez pas, et vous pouvez le désactiver à tout moment.',
|
||||
[`${P}.memory.toggle`]: 'Se souvenir de moi entre les conversations',
|
||||
[`${P}.memory.save`]: 'Enregistrer',
|
||||
[`${P}.memory.saved`]: 'Enregistré.',
|
||||
[`${P}.memory.loading`]: 'Chargement…',
|
||||
[`${P}.memory.unavailable`]: 'La mémoire à long terme ne vous est pas encore accessible. Demandez l’accès à votre administrateur.',
|
||||
[`${P}.memory.soon_title`]: 'Bientôt disponible',
|
||||
[`${P}.memory.soon_body`]: 'Bientôt, vous pourrez demander à Honcho ce qu’il retient de vous et le gérer, directement depuis cette page.',
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,135 @@
|
||||
// Honcho user opt-in page (page_id `memory`, visible to any user with a
|
||||
// `plugin_access` grant).
|
||||
//
|
||||
// The per-user consent to long-term memory. Reuses the core per-user config
|
||||
// endpoints — `GET /api/plugins/mine` to read the current flag,
|
||||
// `PUT /api/plugins/honcho/my-config` to save `{ enabled }` — so this fragment
|
||||
// needs no backend of its own. Structured in sections so the future "what does
|
||||
// Honcho know about me?" panel is a drop-in addition (see the `soon` section).
|
||||
// Default-exports the element class; the host registers it.
|
||||
import { html, nothing } from 'lit';
|
||||
import { HonchoBase, jf, t } from './common.js';
|
||||
|
||||
const P = 'plugin.honcho';
|
||||
const ID = 'honcho';
|
||||
|
||||
export default class HonchoMemoryPage extends HonchoBase {
|
||||
static get properties() {
|
||||
return {
|
||||
_row: { state: true }, // UserPluginView | null (null once loaded = not granted)
|
||||
_enabled: { state: true }, // draft toggle
|
||||
_status: { state: true }, // { ok?, err? }
|
||||
_error: { state: true },
|
||||
_loading: { state: true },
|
||||
};
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this._row = null;
|
||||
this._enabled = false;
|
||||
this._status = {};
|
||||
this._error = null;
|
||||
this._loading = true;
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
super.connectedCallback();
|
||||
this._load();
|
||||
}
|
||||
|
||||
async _load() {
|
||||
this._loading = true;
|
||||
this._error = null;
|
||||
try {
|
||||
const mine = await jf('/api/plugins/mine');
|
||||
const row = (mine ?? []).find(x => x.id === ID) ?? null;
|
||||
this._row = row;
|
||||
this._enabled = !!row?.user_config?.enabled;
|
||||
} catch (e) {
|
||||
this._error = e.message;
|
||||
} finally {
|
||||
this._loading = false;
|
||||
}
|
||||
}
|
||||
|
||||
async _save() {
|
||||
this._status = {};
|
||||
try {
|
||||
await jf(`/api/plugins/${ID}/my-config`, {
|
||||
method: 'PUT',
|
||||
body: JSON.stringify({ enabled: this._enabled }),
|
||||
});
|
||||
this._status = { ok: t(`${P}.memory.saved`) };
|
||||
await this._load();
|
||||
} catch (e) {
|
||||
this._status = { err: e.message };
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<div class="um-page">
|
||||
<div class="um-header">
|
||||
<h2 class="um-title"><i class="bi bi-stars me-2"></i>${t(`${P}.memory.title`)}</h2>
|
||||
</div>
|
||||
<div style="padding:0 1.25rem 2rem; max-width:640px; overflow:auto">
|
||||
${this._error ? html`<div class="alert alert-danger py-2" style="font-size:.85rem">${this._error}</div>` : nothing}
|
||||
${this._loading
|
||||
? html`<div class="um-empty"><i class="bi bi-hourglass-split"></i> ${t(`${P}.memory.loading`)}</div>`
|
||||
: this._row ? this._renderBody() : this._renderUnavailable()}
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
_renderUnavailable() {
|
||||
return html`
|
||||
<div class="um-empty" style="padding:1rem">
|
||||
<i class="bi bi-shield-lock"></i>
|
||||
<p>${t(`${P}.memory.unavailable`)}</p>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
_renderBody() {
|
||||
return html`
|
||||
<p class="text-body-secondary" style="font-size:.9rem">${t(`${P}.memory.intro`)}</p>
|
||||
|
||||
<div class="connector-card" style="cursor:default; border-color:var(--warning, #e0a800)">
|
||||
<div class="connector-card-name" style="font-size:.9rem">
|
||||
<i class="bi bi-exclamation-triangle me-1"></i>${t(`${P}.memory.privacy_title`)}
|
||||
</div>
|
||||
<div class="connector-card-desc" style="-webkit-line-clamp:initial; margin-top:.35rem">
|
||||
${t(`${P}.memory.privacy_body`)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-check form-switch my-3">
|
||||
<input class="form-check-input" type="checkbox" role="switch" id="honcho-optin"
|
||||
.checked=${this._enabled} @change=${(e) => { this._enabled = e.target.checked; this._status = {}; }} />
|
||||
<label class="form-check-label" for="honcho-optin" style="font-size:.9rem">${t(`${P}.memory.toggle`)}</label>
|
||||
</div>
|
||||
|
||||
${this._status.err ? html`<div class="alert alert-danger py-2 mb-3" style="font-size:.82rem">${this._status.err}</div>` : nothing}
|
||||
${this._status.ok ? html`<div class="alert alert-success py-2 mb-3" style="font-size:.82rem">${this._status.ok}</div>` : nothing}
|
||||
|
||||
<button class="btn btn-primary btn-sm" @click=${() => this._save()}>
|
||||
<i class="bi bi-check-lg me-1"></i>${t(`${P}.memory.save`)}
|
||||
</button>
|
||||
|
||||
${this._renderSoon()}`;
|
||||
}
|
||||
|
||||
// Placeholder for the future "what does Honcho know about me?" panel. When
|
||||
// built, this section gains a button that calls a new `GET ${this.api}/whoami`
|
||||
// (opt-in-gated) and renders the returned summary; only this method + that one
|
||||
// route change.
|
||||
_renderSoon() {
|
||||
if (!this._enabled) return nothing;
|
||||
return html`
|
||||
<hr class="my-4" style="opacity:.15" />
|
||||
<div style="opacity:.7">
|
||||
<div style="font-size:.85rem; font-weight:600"><i class="bi bi-hourglass-split me-1"></i>${t(`${P}.memory.soon_title`)}</div>
|
||||
<div class="text-body-secondary" style="font-size:.82rem; margin-top:.25rem">${t(`${P}.memory.soon_body`)}</div>
|
||||
</div>`;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user