@floless/app 0.24.0 → 0.25.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/floless-server.cjs +39 -2
- package/dist/web/app.css +33 -1
- package/dist/web/aware.js +202 -1
- package/dist/web/index.html +18 -3
- package/package.json +1 -1
package/dist/floless-server.cjs
CHANGED
|
@@ -51852,6 +51852,28 @@ var aware = {
|
|
|
51852
51852
|
const { data } = await runJson(["agent", "describe", assertId(id)]);
|
|
51853
51853
|
return data;
|
|
51854
51854
|
},
|
|
51855
|
+
/**
|
|
51856
|
+
* GET /api/agents/available — every agent in the registry catalog (installed or
|
|
51857
|
+
* not), for the Agent Library's "Available" tab. `vendor`/`keywords` (CLI ≥ 0.72,
|
|
51858
|
+
* the catalog-enrichment release) let the UI group by category; older CLIs that
|
|
51859
|
+
* support `catalog` but predate the enrichment simply omit them → the UI falls
|
|
51860
|
+
* back to a flat list. A CLI too old to know `catalog` at all (pre-0.54) throws
|
|
51861
|
+
* AwareUnsupportedError so the caller can say "update AWARE".
|
|
51862
|
+
*/
|
|
51863
|
+
async agentCatalog() {
|
|
51864
|
+
try {
|
|
51865
|
+
const { data } = await runJson(["agent", "catalog"]);
|
|
51866
|
+
return data.agents ?? [];
|
|
51867
|
+
} catch (err) {
|
|
51868
|
+
if (err instanceof AwareError && isInvokeUnsupported(err.detail)) {
|
|
51869
|
+
throw new AwareUnsupportedError(
|
|
51870
|
+
`aware CLI does not support \`agent catalog\` (needs \u2265 0.54) \u2014 update AWARE`,
|
|
51871
|
+
err.detail
|
|
51872
|
+
);
|
|
51873
|
+
}
|
|
51874
|
+
throw err;
|
|
51875
|
+
}
|
|
51876
|
+
},
|
|
51855
51877
|
/**
|
|
51856
51878
|
* Invoke a BUILTIN agent command directly (`aware agent invoke <agent> <command>
|
|
51857
51879
|
* --inputs @file --json`, CLI ≥ 0.63) and return the envelope's `data` — e.g.
|
|
@@ -52755,7 +52777,7 @@ function appVersion() {
|
|
|
52755
52777
|
return resolveVersion({
|
|
52756
52778
|
isSea: isSea2(),
|
|
52757
52779
|
sqVersionXml: readSqVersionXml(),
|
|
52758
|
-
define: true ? "0.
|
|
52780
|
+
define: true ? "0.25.0" : void 0,
|
|
52759
52781
|
pkgVersion: readPkgVersion()
|
|
52760
52782
|
});
|
|
52761
52783
|
}
|
|
@@ -52765,7 +52787,7 @@ function resolveChannel(s) {
|
|
|
52765
52787
|
return "dev";
|
|
52766
52788
|
}
|
|
52767
52789
|
function appChannel() {
|
|
52768
|
-
return resolveChannel({ isSea: isSea2(), define: true ? "0.
|
|
52790
|
+
return resolveChannel({ isSea: isSea2(), define: true ? "0.25.0" : void 0 });
|
|
52769
52791
|
}
|
|
52770
52792
|
|
|
52771
52793
|
// oauth-presets.ts
|
|
@@ -57603,6 +57625,21 @@ async function startServer() {
|
|
|
57603
57625
|
const agent = await aware.agentDescribe(req.params.id);
|
|
57604
57626
|
return { ok: true, agent };
|
|
57605
57627
|
});
|
|
57628
|
+
app.get("/api/agents/available", async () => {
|
|
57629
|
+
try {
|
|
57630
|
+
const agents = await aware.agentCatalog();
|
|
57631
|
+
return { ok: true, agents };
|
|
57632
|
+
} catch (err) {
|
|
57633
|
+
if (err instanceof AwareUnsupportedError) return { ok: true, agents: [], unsupported: true };
|
|
57634
|
+
throw err;
|
|
57635
|
+
}
|
|
57636
|
+
});
|
|
57637
|
+
app.post("/api/agents/install", async (req, reply) => {
|
|
57638
|
+
const id = req.body?.id;
|
|
57639
|
+
if (!id || typeof id !== "string") return reply.status(400).send({ ok: false, error: "id is required" });
|
|
57640
|
+
const result = await aware.ensureAgentInstalled(id);
|
|
57641
|
+
return { ok: true, id: result.installed ?? id };
|
|
57642
|
+
});
|
|
57606
57643
|
app.get("/api/integrations", async () => {
|
|
57607
57644
|
let integrations = listIntegrations();
|
|
57608
57645
|
try {
|
package/dist/web/app.css
CHANGED
|
@@ -1174,6 +1174,38 @@
|
|
|
1174
1174
|
}
|
|
1175
1175
|
.lib-item .lib-fav.faved { color: var(--star); border-color: var(--star); }
|
|
1176
1176
|
|
|
1177
|
+
/* Two-tab Agent Library: the tab bar sits between title and panes; each pane is a
|
|
1178
|
+
flex column so its inner .lib-list keeps scrolling within the modal's max-height.
|
|
1179
|
+
The hidden pane uses [hidden] (display:none) so it's skipped by assistive tech. */
|
|
1180
|
+
.lib-tabs { margin: 4px 0 14px; }
|
|
1181
|
+
/* flex-basis:auto (not 0) so the pane's content height still drives the modal up to
|
|
1182
|
+
its max-height; min-height:0 lets the inner .lib-list (overflow:auto) scroll. With
|
|
1183
|
+
basis:0 the pane contributes no intrinsic height and the modal collapses. */
|
|
1184
|
+
.lib-pane { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; }
|
|
1185
|
+
.lib-pane[hidden] { display: none; }
|
|
1186
|
+
|
|
1187
|
+
/* Available tab: a category band spans both grid columns; the same .lib-item shell
|
|
1188
|
+
as Installed carries the vendor tag, the description blurb, and the Install action. */
|
|
1189
|
+
.lib-section { grid-column: 1 / -1; font-size: 10px; text-transform: uppercase;
|
|
1190
|
+
letter-spacing: 0.12em; color: var(--text-dim); padding: 10px 2px 2px; }
|
|
1191
|
+
.lib-section:first-child { padding-top: 2px; }
|
|
1192
|
+
.lib-vendor { color: var(--text-dim); font-weight: 400; font-size: 11px; }
|
|
1193
|
+
.lib-item .info .blurb {
|
|
1194
|
+
display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
|
|
1195
|
+
}
|
|
1196
|
+
.lib-install {
|
|
1197
|
+
background: transparent; border: 1px solid var(--border-strong); color: var(--text-dim);
|
|
1198
|
+
padding: 4px 11px; font-size: 11px; cursor: pointer; border-radius: 3px; flex-shrink: 0;
|
|
1199
|
+
line-height: 1; transition: color 0.15s, border-color 0.15s;
|
|
1200
|
+
display: inline-flex; align-items: center; gap: 6px;
|
|
1201
|
+
}
|
|
1202
|
+
.lib-install:hover { color: var(--text); border-color: var(--accent-dim); }
|
|
1203
|
+
.lib-install[aria-busy="true"] { color: var(--text-dim); border-color: var(--border-strong); cursor: default; pointer-events: none; }
|
|
1204
|
+
.lib-install .rtn-spinner { width: 11px; height: 11px; border-width: 2px; }
|
|
1205
|
+
.lib-update-link { background: none; border: none; color: var(--accent); cursor: pointer;
|
|
1206
|
+
font-size: 13px; padding: 6px 0 0; font-family: var(--ui); }
|
|
1207
|
+
.lib-update-link:hover { text-decoration: underline; }
|
|
1208
|
+
|
|
1177
1209
|
/* ========== INTEGRATIONS MODAL ========== */
|
|
1178
1210
|
.modal.integrations { width: 760px; max-height: 86vh; display: flex; flex-direction: column; }
|
|
1179
1211
|
.integrations-hint {
|
|
@@ -2518,7 +2550,7 @@ body {
|
|
|
2518
2550
|
|
|
2519
2551
|
/* ===== Tweak modal: screenshot attach ===== */
|
|
2520
2552
|
.fm-file-input { position: absolute; inset: 0; opacity: 0; width: 100%; height: 100%; cursor: pointer; }
|
|
2521
|
-
.fm-drop { position: relative; display: flex; align-items: center; justify-content: center; min-height: 52px; padding: 12px; border: 1px dashed var(--border-strong); border-radius: 4px; background: var(--surface-2); color: var(--text-dim); font-size: 11px; text-align: center; line-height: 1.45; cursor: pointer; transition: border-color 0.15s, background 0.15s, color 0.15s; }
|
|
2553
|
+
.fm-drop { position: relative; display: flex; flex-direction: column; align-items: center; justify-content: center; min-height: 52px; padding: 12px; border: 1px dashed var(--border-strong); border-radius: 4px; background: var(--surface-2); color: var(--text-dim); font-size: 11px; text-align: center; line-height: 1.45; cursor: pointer; transition: border-color 0.15s, background 0.15s, color 0.15s; }
|
|
2522
2554
|
.fm-drop:hover, .fm-drop:focus-visible, .fm-drop.drag-over { border-color: var(--accent-dim); background: var(--accent-soft); color: var(--text-muted); outline: 2px solid var(--accent); outline-offset: 2px; }
|
|
2523
2555
|
.fm-thumbs { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 8px; }
|
|
2524
2556
|
.fm-thumbs[hidden] { display: none; }
|
package/dist/web/aware.js
CHANGED
|
@@ -1628,7 +1628,7 @@
|
|
|
1628
1628
|
const has = st.mode !== 'empty';
|
|
1629
1629
|
drop.classList.toggle('has-file', has);
|
|
1630
1630
|
if (!has) {
|
|
1631
|
-
drop.innerHTML =
|
|
1631
|
+
drop.innerHTML = `<span class="fm-drop-main">Paste (Ctrl+V) or click to attach</span><span class="fm-drop-hint">${escapeHtml(hint)}</span>`;
|
|
1632
1632
|
return;
|
|
1633
1633
|
}
|
|
1634
1634
|
const glyph = st.ext === 'pdf' ? 'pdf' : 'img';
|
|
@@ -3699,6 +3699,8 @@
|
|
|
3699
3699
|
|
|
3700
3700
|
openLibrary = function openLibraryReal() {
|
|
3701
3701
|
$libSearch.value = '';
|
|
3702
|
+
if ($libAvailSearch) $libAvailSearch.value = '';
|
|
3703
|
+
switchLibTab('installed'); // always open on Installed (the primary job)
|
|
3702
3704
|
renderLibrary();
|
|
3703
3705
|
$libModal.classList.add('show');
|
|
3704
3706
|
if (!agentCatalog.length) loadAgentCatalog().then(renderLibrary).catch(reportErr);
|
|
@@ -3762,6 +3764,192 @@
|
|
|
3762
3764
|
} catch (e) { reportErr(e); }
|
|
3763
3765
|
}
|
|
3764
3766
|
|
|
3767
|
+
// ── Agents library: "Available" tab (browse the registry, install on click) ──
|
|
3768
|
+
// Functional categories derived from each agent's registry `keywords` (the CLI
|
|
3769
|
+
// surfaces them in `agent catalog --json` from ≥0.72). First matching category
|
|
3770
|
+
// wins; an agent matching none falls to "Other"; a catalog with NO keywords at
|
|
3771
|
+
// all (older CLI) renders as a flat list (no headers). Categories are a browse
|
|
3772
|
+
// aid only — search always cuts across every agent regardless of category.
|
|
3773
|
+
const AGENT_CATEGORIES = [
|
|
3774
|
+
{ label: 'Structural & Analysis', kw: ['structural', 'analysis', 'engineering', 'fabrication', 'cnc'] },
|
|
3775
|
+
{ label: 'Productivity & Communication', kw: ['messaging', 'chat', 'email', 'calendar', 'teams', 'outlook', 'gmail', 'm365', 'microsoft-365', 'sharepoint', 'onedrive', 'gworkspace'] },
|
|
3776
|
+
{ label: 'Visualization & Viewers', kw: ['visualization', 'viewer', 'rendering', 'webgl', 'digital-twin', '3d'] },
|
|
3777
|
+
{ label: 'Model Checking & Coordination', kw: ['model-checking', 'clash-detection', 'bim-coordination', 'federation', 'inspection', 'validation'] },
|
|
3778
|
+
{ label: 'Construction & Document Control', kw: ['cde', 'document-control', 'construction', 'project-management', 'rfi', 'submittals', 'issues', 'correspondence', 'file-management', 'file-storage', 'drawings', 'specifications', 'spec', 'markup', 'masterformat', 'classification', 'ribadrm', 'sharing', 'redline'] },
|
|
3779
|
+
{ label: 'Interoperability & Formats', kw: ['ifc', 'bcf', 'dstv', 'nc1', 'file-format', 'cobie', 'openbim', 'bcf-xml'] },
|
|
3780
|
+
{ label: 'BIM Authoring & CAD', kw: ['architecture', 'bim', 'cad', 'drafting', 'parametric'] },
|
|
3781
|
+
{ label: 'Web, Data & Automation', kw: ['rest', 'http', 'api', 'webhook', 'openapi', 'json', 'vision', 'extract', 'escape-hatch'] },
|
|
3782
|
+
{ label: 'AWARE Tooling', kw: ['meta', 'builder', 'skill', 'descriptor', 'panels', 'blocks', 'introspection', 'uiautomation', 'codegen', 'agent'] },
|
|
3783
|
+
];
|
|
3784
|
+
const OTHER_CATEGORY = 'Other';
|
|
3785
|
+
|
|
3786
|
+
function categorizeAgent(a) {
|
|
3787
|
+
const kws = a.keywords || [];
|
|
3788
|
+
for (const cat of AGENT_CATEGORIES) {
|
|
3789
|
+
if (kws.some((k) => cat.kw.includes(k))) return cat.label;
|
|
3790
|
+
}
|
|
3791
|
+
return OTHER_CATEGORY;
|
|
3792
|
+
}
|
|
3793
|
+
|
|
3794
|
+
let availableCatalog = []; // every registry agent (installed or not)
|
|
3795
|
+
let availableLoaded = false; // first-switch lazy-load guard (set once loaded)
|
|
3796
|
+
let availableLoading = false; // a fetch is in flight (prevents a double-fetch race)
|
|
3797
|
+
let availableUnsupported = false; // CLI too old to know `agent catalog`
|
|
3798
|
+
let libTab = 'installed';
|
|
3799
|
+
|
|
3800
|
+
async function loadAvailableAgents() {
|
|
3801
|
+
const { agents, unsupported } = await api('/api/agents/available');
|
|
3802
|
+
availableCatalog = agents || [];
|
|
3803
|
+
availableUnsupported = !!unsupported;
|
|
3804
|
+
availableLoaded = true;
|
|
3805
|
+
}
|
|
3806
|
+
|
|
3807
|
+
// "Available" means available-to-install, so already-installed agents live only
|
|
3808
|
+
// in the Installed tab. agentCatalog holds the installed set (id per item).
|
|
3809
|
+
function installedIdSet() { return new Set(agentCatalog.map((a) => a.id)); }
|
|
3810
|
+
|
|
3811
|
+
function availCardHtml(a) {
|
|
3812
|
+
const name = escapeHtml(a.display_name || a.id);
|
|
3813
|
+
const vendor = a.vendor ? ` <span class="lib-vendor">· ${escapeHtml(a.vendor)}</span>` : '';
|
|
3814
|
+
const desc = (a.description || '').trim();
|
|
3815
|
+
const blurb = desc ? `<div class="blurb">${escapeHtml(desc)}</div>` : '';
|
|
3816
|
+
// Counts come from the CLI; default to 0 so an older CLI omitting them never
|
|
3817
|
+
// renders "undefined command". (Numbers, so no escaping needed.)
|
|
3818
|
+
const cmds = Number(a.commands) || 0;
|
|
3819
|
+
const skills = Number(a.skills) || 0;
|
|
3820
|
+
return `
|
|
3821
|
+
<div class="lib-card" data-agent="${escapeAttr(a.id)}">
|
|
3822
|
+
<div class="lib-item">
|
|
3823
|
+
<div class="info">
|
|
3824
|
+
<div class="name">${name}${vendor}</div>
|
|
3825
|
+
<div class="meta">${cmds} command${cmds === 1 ? '' : 's'} · ${skills} skill${skills === 1 ? '' : 's'}</div>
|
|
3826
|
+
${blurb}
|
|
3827
|
+
</div>
|
|
3828
|
+
<button class="lib-install" data-install="${escapeAttr(a.id)}" aria-label="Install ${escapeAttr(a.display_name || a.id)}">Install</button>
|
|
3829
|
+
</div>
|
|
3830
|
+
</div>`;
|
|
3831
|
+
}
|
|
3832
|
+
|
|
3833
|
+
function renderAvailable() {
|
|
3834
|
+
// Degradation (i): CLI predates `agent catalog` entirely — hide search, show notice.
|
|
3835
|
+
if (availableUnsupported) {
|
|
3836
|
+
$libAvailSearch.hidden = true;
|
|
3837
|
+
$libAvailList.innerHTML =
|
|
3838
|
+
'<div class="empty-state">Browsing available agents needs AWARE 0.54 or later.' +
|
|
3839
|
+
'<br><button class="lib-update-link" id="lib-avail-update">Update AWARE →</button></div>';
|
|
3840
|
+
const up = document.getElementById('lib-avail-update');
|
|
3841
|
+
if (up) up.onclick = () => {
|
|
3842
|
+
hideModal($libModal);
|
|
3843
|
+
const pill = document.getElementById('aware-update');
|
|
3844
|
+
if (pill && !pill.hidden) pill.click();
|
|
3845
|
+
else showToast('Update AWARE (@aware-aeco/cli) to 0.54+ to browse available agents', 'info');
|
|
3846
|
+
};
|
|
3847
|
+
$libAvailCount.textContent = '';
|
|
3848
|
+
return;
|
|
3849
|
+
}
|
|
3850
|
+
$libAvailSearch.hidden = false;
|
|
3851
|
+
|
|
3852
|
+
if (!availableLoaded) {
|
|
3853
|
+
$libAvailList.innerHTML = '<div class="empty-state"><span class="rtn-spinner"></span> Loading available agents…</div>';
|
|
3854
|
+
$libAvailCount.textContent = '';
|
|
3855
|
+
return;
|
|
3856
|
+
}
|
|
3857
|
+
|
|
3858
|
+
const installed = installedIdSet();
|
|
3859
|
+
const notInstalled = availableCatalog.filter((a) => !installed.has(a.id));
|
|
3860
|
+
const q = ($libAvailSearch.value || '').toLowerCase().trim();
|
|
3861
|
+
const match = (a) => !q
|
|
3862
|
+
|| a.id.toLowerCase().includes(q)
|
|
3863
|
+
|| (a.display_name || '').toLowerCase().includes(q)
|
|
3864
|
+
|| (a.description || '').toLowerCase().includes(q)
|
|
3865
|
+
|| (a.vendor || '').toLowerCase().includes(q)
|
|
3866
|
+
|| (a.keywords || []).some((k) => k.toLowerCase().includes(q));
|
|
3867
|
+
const items = notInstalled.filter(match);
|
|
3868
|
+
$libAvailCount.textContent = `${items.length} agent${items.length === 1 ? '' : 's'}`;
|
|
3869
|
+
|
|
3870
|
+
if (!items.length) {
|
|
3871
|
+
$libAvailList.innerHTML = '<div class="empty-state">No agents match.</div>';
|
|
3872
|
+
return;
|
|
3873
|
+
}
|
|
3874
|
+
|
|
3875
|
+
const hasKeywords = items.some((a) => (a.keywords || []).length);
|
|
3876
|
+
let html;
|
|
3877
|
+
if (q || !hasKeywords) {
|
|
3878
|
+
// Searching, or no keyword data (older CLI) → one flat list, no headers.
|
|
3879
|
+
html = items.map(availCardHtml).join('');
|
|
3880
|
+
} else {
|
|
3881
|
+
// Browse mode: group by category in AGENT_CATEGORIES order, "Other" last.
|
|
3882
|
+
const order = [...AGENT_CATEGORIES.map((c) => c.label), OTHER_CATEGORY];
|
|
3883
|
+
const byCat = new Map();
|
|
3884
|
+
for (const a of items) {
|
|
3885
|
+
const c = categorizeAgent(a);
|
|
3886
|
+
if (!byCat.has(c)) byCat.set(c, []);
|
|
3887
|
+
byCat.get(c).push(a);
|
|
3888
|
+
}
|
|
3889
|
+
html = order.filter((c) => byCat.has(c)).map((c) =>
|
|
3890
|
+
`<div class="lib-section">${escapeHtml(c)}</div>` + byCat.get(c).map(availCardHtml).join('')
|
|
3891
|
+
).join('');
|
|
3892
|
+
}
|
|
3893
|
+
$libAvailList.innerHTML = html;
|
|
3894
|
+
$libAvailList.querySelectorAll('[data-install]').forEach((btn) => {
|
|
3895
|
+
btn.onclick = () => installAgent(btn.dataset.install, btn);
|
|
3896
|
+
});
|
|
3897
|
+
}
|
|
3898
|
+
|
|
3899
|
+
async function installAgent(id, btn) {
|
|
3900
|
+
const label = btn.getAttribute('aria-label') || `Install ${id}`;
|
|
3901
|
+
const a = availableCatalog.find((x) => x.id === id);
|
|
3902
|
+
btn.setAttribute('aria-busy', 'true');
|
|
3903
|
+
btn.setAttribute('aria-label', label.replace(/^Install/, 'Installing') + '…');
|
|
3904
|
+
btn.innerHTML = '<span class="rtn-spinner"></span>';
|
|
3905
|
+
try {
|
|
3906
|
+
await api('/api/agents/install', { method: 'POST', body: JSON.stringify({ id }) });
|
|
3907
|
+
} catch (e) {
|
|
3908
|
+
btn.removeAttribute('aria-busy');
|
|
3909
|
+
btn.setAttribute('aria-label', label);
|
|
3910
|
+
btn.textContent = 'Install';
|
|
3911
|
+
reportErr(e);
|
|
3912
|
+
return;
|
|
3913
|
+
}
|
|
3914
|
+
// Installed now — signal success BEFORE the catalog refresh so a transient refresh
|
|
3915
|
+
// failure can't swallow the confirmation of an install that already succeeded.
|
|
3916
|
+
showToast(`Installed “${a && a.display_name ? a.display_name : id}” — find it in Installed`, 'ok');
|
|
3917
|
+
// Refresh the Installed catalog (footer count + Installed tab) best-effort, then
|
|
3918
|
+
// re-render Available so the now-installed agent drops out (= not-yet-installed).
|
|
3919
|
+
try { await loadAgentCatalog(); } catch (e) { console.warn('post-install catalog refresh failed', e); }
|
|
3920
|
+
renderAvailable();
|
|
3921
|
+
if (libTab === 'installed') renderLibrary();
|
|
3922
|
+
}
|
|
3923
|
+
|
|
3924
|
+
function switchLibTab(tab) {
|
|
3925
|
+
libTab = tab;
|
|
3926
|
+
$libTabs.querySelectorAll('[data-libtab]').forEach((b) => {
|
|
3927
|
+
const on = b.dataset.libtab === tab;
|
|
3928
|
+
b.classList.toggle('active', on);
|
|
3929
|
+
b.setAttribute('aria-selected', on ? 'true' : 'false');
|
|
3930
|
+
});
|
|
3931
|
+
$libPaneInstalled.hidden = tab !== 'installed';
|
|
3932
|
+
$libPaneAvailable.hidden = tab !== 'available';
|
|
3933
|
+
if (tab === 'available') {
|
|
3934
|
+
renderAvailable(); // shows the loading spinner first if not yet loaded
|
|
3935
|
+
if (!availableLoaded && !availableLoading && !availableUnsupported) {
|
|
3936
|
+
availableLoading = true;
|
|
3937
|
+
loadAvailableAgents()
|
|
3938
|
+
.then(renderAvailable)
|
|
3939
|
+
.catch((e) => {
|
|
3940
|
+
$libAvailList.innerHTML = '<div class="empty-state">Could not load available agents.</div>';
|
|
3941
|
+
$libAvailCount.textContent = '';
|
|
3942
|
+
reportErr(e);
|
|
3943
|
+
})
|
|
3944
|
+
.finally(() => { availableLoading = false; });
|
|
3945
|
+
}
|
|
3946
|
+
setTimeout(() => { if (!$libAvailSearch.hidden) $libAvailSearch.focus(); }, 0);
|
|
3947
|
+
} else {
|
|
3948
|
+
renderLibrary(); // repaint from agentCatalog — it may have grown via an Install on the other tab
|
|
3949
|
+
setTimeout(() => $libSearch.focus(), 0);
|
|
3950
|
+
}
|
|
3951
|
+
}
|
|
3952
|
+
|
|
3765
3953
|
// ── Integrations: real connection state from the local vault ────────────────
|
|
3766
3954
|
const VENDOR_ICON = { microsoft: '▦', google: 'G', trimble: '◬', autodesk: 'A' };
|
|
3767
3955
|
let integrations = [];
|
|
@@ -4755,6 +4943,19 @@
|
|
|
4755
4943
|
$browseBtn.onclick = () => openLibrary();
|
|
4756
4944
|
$libSearch.oninput = () => renderLibrary();
|
|
4757
4945
|
|
|
4946
|
+
// Agent Library two-tab wiring (Installed | Available). The Available tab is
|
|
4947
|
+
// lazy-loaded on first switch (see switchLibTab).
|
|
4948
|
+
const $libTabs = document.getElementById('lib-tabs');
|
|
4949
|
+
const $libPaneInstalled = document.getElementById('lib-pane-installed');
|
|
4950
|
+
const $libPaneAvailable = document.getElementById('lib-pane-available');
|
|
4951
|
+
const $libAvailSearch = document.getElementById('lib-avail-search');
|
|
4952
|
+
const $libAvailList = document.getElementById('lib-avail-list');
|
|
4953
|
+
const $libAvailCount = document.getElementById('lib-avail-count');
|
|
4954
|
+
if ($libTabs) $libTabs.querySelectorAll('[data-libtab]').forEach((b) => {
|
|
4955
|
+
b.onclick = () => switchLibTab(b.dataset.libtab);
|
|
4956
|
+
});
|
|
4957
|
+
if ($libAvailSearch) $libAvailSearch.oninput = () => renderAvailable();
|
|
4958
|
+
|
|
4758
4959
|
// Compile-notes strip: × collapses it to a faint one-line pill; clicking the
|
|
4759
4960
|
// collapsed pill re-expands. Delegated (the strip's innerHTML is rebuilt each
|
|
4760
4961
|
// load). The choice is remembered per app+note-signature (see notesSig).
|
package/dist/web/index.html
CHANGED
|
@@ -416,9 +416,24 @@
|
|
|
416
416
|
<div class="modal-backdrop" id="lib-modal">
|
|
417
417
|
<div class="modal library">
|
|
418
418
|
<div class="modal-title">Agent Library</div>
|
|
419
|
-
<div class="
|
|
420
|
-
|
|
421
|
-
|
|
419
|
+
<div class="tabs lib-tabs" id="lib-tabs" role="tablist" aria-label="Agent Library tabs">
|
|
420
|
+
<button data-libtab="installed" class="active" role="tab" aria-selected="true" aria-controls="lib-pane-installed">Installed</button>
|
|
421
|
+
<button data-libtab="available" role="tab" aria-selected="false" aria-controls="lib-pane-available">Available</button>
|
|
422
|
+
</div>
|
|
423
|
+
|
|
424
|
+
<div class="lib-pane" id="lib-pane-installed" role="tabpanel" aria-label="Installed agents">
|
|
425
|
+
<div class="modal-sub">All installed agents · star a command to save it as a Template.</div>
|
|
426
|
+
<input id="lib-search" class="lib-search" type="text" placeholder="Search (tekla, slack, file, …)" aria-label="Search installed agents">
|
|
427
|
+
<div class="lib-list" id="lib-list"></div>
|
|
428
|
+
</div>
|
|
429
|
+
|
|
430
|
+
<div class="lib-pane" id="lib-pane-available" role="tabpanel" aria-label="Available agents" hidden>
|
|
431
|
+
<div class="modal-sub">Every agent in the AWARE registry · install one to add it to your library.</div>
|
|
432
|
+
<input id="lib-avail-search" class="lib-search" type="text" placeholder="Search available agents (revit, ifc, slack, …)" aria-label="Search available agents">
|
|
433
|
+
<span class="sr-only" aria-live="polite" id="lib-avail-count"></span>
|
|
434
|
+
<div class="lib-list" id="lib-avail-list"></div>
|
|
435
|
+
</div>
|
|
436
|
+
|
|
422
437
|
<div class="modal-actions">
|
|
423
438
|
<button id="lib-graft" style="margin-right:auto;" data-tip="Build a new agent from a tool you own — a DLL, C# source, NuGet package, or API spec.">⊕ Graft new agent</button>
|
|
424
439
|
<button id="lib-close">Close</button>
|