@floless/app 0.24.1 → 0.25.1
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 +74 -6
- package/dist/web/app.css +32 -0
- package/dist/web/aware.js +201 -0
- 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.1" : 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.1" : void 0 });
|
|
52769
52791
|
}
|
|
52770
52792
|
|
|
52771
52793
|
// oauth-presets.ts
|
|
@@ -54819,12 +54841,43 @@ async function shareReport(input) {
|
|
|
54819
54841
|
// release-notes.ts
|
|
54820
54842
|
var FETCH_TIMEOUT_MS = 8e3;
|
|
54821
54843
|
var AWARE_REPO = "aware-aeco/aware";
|
|
54822
|
-
var
|
|
54844
|
+
var STYLED_CHANGE_RE = /^[-*]\s+\*\*?(Added|Changed|Fixed|Removed|Security)\*\*?:\s+(.+)$/gim;
|
|
54845
|
+
var COMMIT_BULLET_RE = /^[-*][ \t]+(?!\*)(.+?)\s*$/gm;
|
|
54846
|
+
var GH_CREDIT_RE = / by @[\w-]+ in https?:\/\/\S+$/;
|
|
54847
|
+
var CC_PREFIX_RE = /^(\w+)(?:\([^)]*\))?!?:\s*(.+)$/;
|
|
54848
|
+
function ccType(prefix) {
|
|
54849
|
+
switch (prefix.toLowerCase()) {
|
|
54850
|
+
case "feat":
|
|
54851
|
+
return "added";
|
|
54852
|
+
case "fix":
|
|
54853
|
+
return "fixed";
|
|
54854
|
+
case "security":
|
|
54855
|
+
case "sec":
|
|
54856
|
+
return "security";
|
|
54857
|
+
case "remove":
|
|
54858
|
+
case "removed":
|
|
54859
|
+
return "removed";
|
|
54860
|
+
default:
|
|
54861
|
+
return "changed";
|
|
54862
|
+
}
|
|
54863
|
+
}
|
|
54823
54864
|
function parseBulletChanges(body) {
|
|
54824
|
-
const
|
|
54865
|
+
const text = body.replace(/\r\n?/g, "\n");
|
|
54866
|
+
const styled = [];
|
|
54825
54867
|
let m;
|
|
54826
|
-
|
|
54827
|
-
while ((m =
|
|
54868
|
+
STYLED_CHANGE_RE.lastIndex = 0;
|
|
54869
|
+
while ((m = STYLED_CHANGE_RE.exec(text)) !== null) styled.push({ type: m[1].toLowerCase(), description: m[2].trim() });
|
|
54870
|
+
if (styled.length) return styled;
|
|
54871
|
+
const out = [];
|
|
54872
|
+
COMMIT_BULLET_RE.lastIndex = 0;
|
|
54873
|
+
while ((m = COMMIT_BULLET_RE.exec(text)) !== null) {
|
|
54874
|
+
const raw = m[1].replace(GH_CREDIT_RE, "").trim();
|
|
54875
|
+
if (!raw) continue;
|
|
54876
|
+
if (raw.startsWith("@")) continue;
|
|
54877
|
+
if (/^chore\(release\)/i.test(raw)) continue;
|
|
54878
|
+
const cc = CC_PREFIX_RE.exec(raw);
|
|
54879
|
+
out.push(cc ? { type: ccType(cc[1]), description: cc[2].trim() } : { type: "changed", description: raw });
|
|
54880
|
+
}
|
|
54828
54881
|
return out;
|
|
54829
54882
|
}
|
|
54830
54883
|
var cache = /* @__PURE__ */ new Map();
|
|
@@ -57603,6 +57656,21 @@ async function startServer() {
|
|
|
57603
57656
|
const agent = await aware.agentDescribe(req.params.id);
|
|
57604
57657
|
return { ok: true, agent };
|
|
57605
57658
|
});
|
|
57659
|
+
app.get("/api/agents/available", async () => {
|
|
57660
|
+
try {
|
|
57661
|
+
const agents = await aware.agentCatalog();
|
|
57662
|
+
return { ok: true, agents };
|
|
57663
|
+
} catch (err) {
|
|
57664
|
+
if (err instanceof AwareUnsupportedError) return { ok: true, agents: [], unsupported: true };
|
|
57665
|
+
throw err;
|
|
57666
|
+
}
|
|
57667
|
+
});
|
|
57668
|
+
app.post("/api/agents/install", async (req, reply) => {
|
|
57669
|
+
const id = req.body?.id;
|
|
57670
|
+
if (!id || typeof id !== "string") return reply.status(400).send({ ok: false, error: "id is required" });
|
|
57671
|
+
const result = await aware.ensureAgentInstalled(id);
|
|
57672
|
+
return { ok: true, id: result.installed ?? id };
|
|
57673
|
+
});
|
|
57606
57674
|
app.get("/api/integrations", async () => {
|
|
57607
57675
|
let integrations = listIntegrations();
|
|
57608
57676
|
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 {
|
package/dist/web/aware.js
CHANGED
|
@@ -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>
|