aimeat 1.16.2 → 1.18.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/.env.example +6 -1
- package/dist/locales/en.json +166 -4
- package/dist/locales/fi.json +166 -4
- package/dist/public/components/Card.js +17 -3
- package/dist/public/components/Collapsible.js +39 -0
- package/dist/public/components/CopyButton.js +55 -24
- package/dist/public/components/DataTable.js +57 -0
- package/dist/public/components/EmptyState.js +28 -0
- package/dist/public/components/KeyValueRow.js +37 -0
- package/dist/public/components/Modal.js +22 -5
- package/dist/public/components/Pagination.js +70 -0
- package/dist/public/components/SearchBar.js +44 -0
- package/dist/public/components/StatusDot.js +29 -0
- package/dist/public/components/TagList.js +53 -0
- package/dist/public/components/ToggleSwitch.js +28 -0
- package/dist/public/components/index.js +41 -1
- package/dist/public/css/components/tags.css +24 -0
- package/dist/public/css/style-testbed.css +135 -0
- package/dist/public/css/theme.css +556 -34
- package/dist/public/css/views/admin-agent-integration.css +2 -33
- package/dist/public/css/views/admin.css +730 -686
- package/dist/public/css/views/agents-detail.css +402 -14
- package/dist/public/css/views/help.css +2 -1
- package/dist/public/css/views/hobbies.css +53 -33
- package/dist/public/css/views/marketplace.css +73 -60
- package/dist/public/css/views/openclaw.css +12 -6
- package/dist/public/css/views/portal-classic.css +34 -26
- package/dist/public/css/views/portal-dev.css +41 -32
- package/dist/public/css/views/portal.css +14 -18
- package/dist/public/css/views/profile.css +252 -164
- package/dist/public/css/views/public-knowledge-viewer.css +18 -6
- package/dist/public/css/views/scheduler.css +100 -0
- package/dist/public/js/format.js +41 -0
- package/dist/public/js/services/agent-tasks.js +14 -1
- package/dist/public/js/services/generator-spec-validate.js +5 -1
- package/dist/public/js/services/schedules.js +53 -0
- package/dist/public/js/style-testbed.js +170 -0
- package/dist/public/js/theme.js +114 -0
- package/dist/public/spa.html +74 -16
- package/dist/public/style-testbed.html +420 -0
- package/dist/public/views/admin/agent-integration-tab.js +23 -38
- package/dist/public/views/admin/agent-tasks-tab.js +14 -21
- package/dist/public/views/admin/chat-instances-tab.js +1 -1
- package/dist/public/views/admin/config-tab.js +7 -2
- package/dist/public/views/admin/cortex-tab.js +20 -14
- package/dist/public/views/admin/csm-tab.js +22 -18
- package/dist/public/views/admin/directory-tab.js +3 -3
- package/dist/public/views/admin/email-tab.js +23 -28
- package/dist/public/views/admin/federation-tab.js +15 -5
- package/dist/public/views/admin/generator-debug-tab.js +10 -8
- package/dist/public/views/admin/genesis-tab.js +4 -4
- package/dist/public/views/admin/ghii-tab.js +3 -3
- package/dist/public/views/admin/knowledge-tab.js +15 -5
- package/dist/public/views/admin/matching-tab.js +3 -3
- package/dist/public/views/admin/memory-tab.js +16 -20
- package/dist/public/views/admin/msm-tab.js +30 -17
- package/dist/public/views/admin/overview-tab.js +7 -7
- package/dist/public/views/admin/packages-tab.js +10 -9
- package/dist/public/views/admin/portal-tab.js +14 -4
- package/dist/public/views/admin/prompts-tab.js +5 -5
- package/dist/public/views/admin/push-tab.js +15 -4
- package/dist/public/views/admin/realtime-tab.js +3 -3
- package/dist/public/views/admin/services-tab.js +18 -27
- package/dist/public/views/admin/shared.js +48 -58
- package/dist/public/views/admin/sharing-groups-tab.js +5 -10
- package/dist/public/views/admin/stats-tab.js +3 -3
- package/dist/public/views/guides.js +9 -13
- package/dist/public/views/help.js +3 -24
- package/dist/public/views/hobbies.js +47 -41
- package/dist/public/views/marketplace.js +42 -34
- package/dist/public/views/openclaw.js +0 -1
- package/dist/public/views/portal-classic.js +4 -15
- package/dist/public/views/portal-dev.js +28 -30
- package/dist/public/views/portal.js +16 -21
- package/dist/public/views/portfolio.js +11 -12
- package/dist/public/views/profile/access-tab.js +250 -1
- package/dist/public/views/profile/agents/agent-card.js +75 -4
- package/dist/public/views/profile/agents/tab-activity.js +15 -13
- package/dist/public/views/profile/agents/tab-agent-config.js +60 -6
- package/dist/public/views/profile/agents/tab-integration.js +37 -60
- package/dist/public/views/profile/agents/tab-messages.js +10 -1
- package/dist/public/views/profile/agents/tab-quality.js +25 -19
- package/dist/public/views/profile/agents/tab-schedules.js +110 -0
- package/dist/public/views/profile/agents-activity-subtab.js +4 -4
- package/dist/public/views/profile/agents-services-subtab.js +5 -4
- package/dist/public/views/profile/agents-tab.js +141 -37
- package/dist/public/views/profile/agents-tasks-subtab.js +221 -26
- package/dist/public/views/profile/calibrator-batch.js +5 -11
- package/dist/public/views/profile/calibrator-tab.js +4 -9
- package/dist/public/views/profile/chat-sessions-tab.js +9 -5
- package/dist/public/views/profile/email-tab.js +2 -2
- package/dist/public/views/profile/extensions-tab.js +27 -22
- package/dist/public/views/profile/federation-tab.js +4 -1
- package/dist/public/views/profile/foundry-detail.js +22 -23
- package/dist/public/views/profile/foundry-tab.js +12 -13
- package/dist/public/views/profile/generator-dashboard/DebugPanel.js +4 -7
- package/dist/public/views/profile/generator-dashboard/use-dashboard-core.js +4 -1
- package/dist/public/views/profile/generator-dashboard/use-edit-mode.js +3 -2
- package/dist/public/views/profile/generator-detail.js +26 -30
- package/dist/public/views/profile/generator-tab.js +5 -5
- package/dist/public/views/profile/knowledge-tab.js +12 -12
- package/dist/public/views/profile/landing-page.js +100 -149
- package/dist/public/views/profile/memory-tab.js +15 -11
- package/dist/public/views/profile/node-stats-tab.js +3 -3
- package/dist/public/views/profile/nodes-tab.js +10 -3
- package/dist/public/views/profile/notifications-tab.js +3 -3
- package/dist/public/views/profile/packages-tab.js +14 -14
- package/dist/public/views/profile/scheduler-tab.js +340 -0
- package/dist/public/views/profile/services-tab.js +1 -1
- package/dist/public/views/profile/shared.js +16 -12
- package/dist/public/views/profile/wallet-tab.js +27 -50
- package/dist/public/views/profile/work-tab.js +29 -31
- package/dist/public/views/profile.js +24 -0
- package/dist/public/views/public-knowledge-viewer.js +9 -18
- package/dist/src/auth/middleware.d.ts +3 -2
- package/dist/src/auth/middleware.d.ts.map +1 -1
- package/dist/src/auth/middleware.js +78 -3
- package/dist/src/auth/middleware.js.map +1 -1
- package/dist/src/cli/init-wizard.d.ts.map +1 -1
- package/dist/src/cli/init-wizard.js +13 -1
- package/dist/src/cli/init-wizard.js.map +1 -1
- package/dist/src/config.d.ts +6 -0
- package/dist/src/config.d.ts.map +1 -1
- package/dist/src/config.js +12 -1
- package/dist/src/config.js.map +1 -1
- package/dist/src/generated/api-types.d.ts +708 -8
- package/dist/src/generated/api-types.d.ts.map +1 -1
- package/dist/src/mcp/agent-schedules.d.ts +23 -0
- package/dist/src/mcp/agent-schedules.d.ts.map +1 -0
- package/dist/src/mcp/agent-schedules.js +175 -0
- package/dist/src/mcp/agent-schedules.js.map +1 -0
- package/dist/src/mcp/annotations.d.ts.map +1 -1
- package/dist/src/mcp/annotations.js +6 -0
- package/dist/src/mcp/annotations.js.map +1 -1
- package/dist/src/mcp/catalog/definitions.d.ts.map +1 -1
- package/dist/src/mcp/catalog/definitions.js +55 -0
- package/dist/src/mcp/catalog/definitions.js.map +1 -1
- package/dist/src/mcp/catalog/scopes.d.ts.map +1 -1
- package/dist/src/mcp/catalog/scopes.js +12 -0
- package/dist/src/mcp/catalog/scopes.js.map +1 -1
- package/dist/src/mcp/catalog/surfaces.d.ts.map +1 -1
- package/dist/src/mcp/catalog/surfaces.js +2 -0
- package/dist/src/mcp/catalog/surfaces.js.map +1 -1
- package/dist/src/mcp/index.d.ts.map +1 -1
- package/dist/src/mcp/index.js +2 -0
- package/dist/src/mcp/index.js.map +1 -1
- package/dist/src/models/agent-task-schemas.d.ts +6 -0
- package/dist/src/models/agent-task-schemas.d.ts.map +1 -1
- package/dist/src/models/agent-task-schemas.js +6 -0
- package/dist/src/models/agent-task-schemas.js.map +1 -1
- package/dist/src/models/schemas.d.ts +1 -0
- package/dist/src/models/schemas.d.ts.map +1 -1
- package/dist/src/models/schemas.js +4 -0
- package/dist/src/models/schemas.js.map +1 -1
- package/dist/src/routes/access-tokens.d.ts +17 -0
- package/dist/src/routes/access-tokens.d.ts.map +1 -0
- package/dist/src/routes/access-tokens.js +164 -0
- package/dist/src/routes/access-tokens.js.map +1 -0
- package/dist/src/routes/agent-tasks.d.ts +2 -0
- package/dist/src/routes/agent-tasks.d.ts.map +1 -1
- package/dist/src/routes/agent-tasks.js +118 -23
- package/dist/src/routes/agent-tasks.js.map +1 -1
- package/dist/src/routes/agents.d.ts.map +1 -1
- package/dist/src/routes/agents.js +46 -0
- package/dist/src/routes/agents.js.map +1 -1
- package/dist/src/routes/ai.d.ts +6 -9
- package/dist/src/routes/ai.d.ts.map +1 -1
- package/dist/src/routes/ai.js +26 -175
- package/dist/src/routes/ai.js.map +1 -1
- package/dist/src/routes/apps.d.ts.map +1 -1
- package/dist/src/routes/apps.js +104 -34
- package/dist/src/routes/apps.js.map +1 -1
- package/dist/src/routes/auth.d.ts.map +1 -1
- package/dist/src/routes/auth.js +77 -4
- package/dist/src/routes/auth.js.map +1 -1
- package/dist/src/routes/cortex.d.ts +2 -1
- package/dist/src/routes/cortex.d.ts.map +1 -1
- package/dist/src/routes/cortex.js +13 -8
- package/dist/src/routes/cortex.js.map +1 -1
- package/dist/src/routes/extensions.d.ts.map +1 -1
- package/dist/src/routes/extensions.js +85 -51
- package/dist/src/routes/extensions.js.map +1 -1
- package/dist/src/routes/generator.d.ts.map +1 -1
- package/dist/src/routes/generator.js +109 -43
- package/dist/src/routes/generator.js.map +1 -1
- package/dist/src/routes/ghii.d.ts.map +1 -1
- package/dist/src/routes/ghii.js +34 -13
- package/dist/src/routes/ghii.js.map +1 -1
- package/dist/src/routes/libs.d.ts +15 -0
- package/dist/src/routes/libs.d.ts.map +1 -1
- package/dist/src/routes/libs.js +178 -55
- package/dist/src/routes/libs.js.map +1 -1
- package/dist/src/routes/portal.d.ts.map +1 -1
- package/dist/src/routes/portal.js +1 -0
- package/dist/src/routes/portal.js.map +1 -1
- package/dist/src/routes/schedules.d.ts +33 -0
- package/dist/src/routes/schedules.d.ts.map +1 -0
- package/dist/src/routes/schedules.js +380 -0
- package/dist/src/routes/schedules.js.map +1 -0
- package/dist/src/server-bootstrap/config-init.js +2 -2
- package/dist/src/server-bootstrap/config-init.js.map +1 -1
- package/dist/src/server-bootstrap/routes-loader.d.ts.map +1 -1
- package/dist/src/server-bootstrap/routes-loader.js +7 -0
- package/dist/src/server-bootstrap/routes-loader.js.map +1 -1
- package/dist/src/server-bootstrap/service-init.d.ts.map +1 -1
- package/dist/src/server-bootstrap/service-init.js +2 -1
- package/dist/src/server-bootstrap/service-init.js.map +1 -1
- package/dist/src/services/access-token.d.ts +28 -0
- package/dist/src/services/access-token.d.ts.map +1 -0
- package/dist/src/services/access-token.js +37 -0
- package/dist/src/services/access-token.js.map +1 -0
- package/dist/src/services/ai-completion.d.ts +83 -0
- package/dist/src/services/ai-completion.d.ts.map +1 -0
- package/dist/src/services/ai-completion.js +187 -0
- package/dist/src/services/ai-completion.js.map +1 -0
- package/dist/src/services/config-schema.d.ts.map +1 -1
- package/dist/src/services/config-schema.js +3 -0
- package/dist/src/services/config-schema.js.map +1 -1
- package/dist/src/services/generator-prompt-seeds.d.ts.map +1 -1
- package/dist/src/services/generator-prompt-seeds.js +6 -3
- package/dist/src/services/generator-prompt-seeds.js.map +1 -1
- package/dist/src/services/generator-prompts/spec-validate.d.ts.map +1 -1
- package/dist/src/services/generator-prompts/spec-validate.js +6 -2
- package/dist/src/services/generator-prompts/spec-validate.js.map +1 -1
- package/dist/src/services/generator-registration.d.ts +12 -4
- package/dist/src/services/generator-registration.d.ts.map +1 -1
- package/dist/src/services/generator-registration.js +71 -0
- package/dist/src/services/generator-registration.js.map +1 -1
- package/dist/src/services/generator-validate.d.ts +10 -0
- package/dist/src/services/generator-validate.d.ts.map +1 -1
- package/dist/src/services/generator-validate.js +180 -36
- package/dist/src/services/generator-validate.js.map +1 -1
- package/dist/src/services/owner-session.d.ts +43 -0
- package/dist/src/services/owner-session.d.ts.map +1 -0
- package/dist/src/services/owner-session.js +175 -0
- package/dist/src/services/owner-session.js.map +1 -0
- package/dist/src/services/schedule-constraints.d.ts +65 -0
- package/dist/src/services/schedule-constraints.d.ts.map +1 -0
- package/dist/src/services/schedule-constraints.js +106 -0
- package/dist/src/services/schedule-constraints.js.map +1 -0
- package/dist/src/services/scheduler.d.ts +34 -0
- package/dist/src/services/scheduler.d.ts.map +1 -1
- package/dist/src/services/scheduler.js +266 -22
- package/dist/src/services/scheduler.js.map +1 -1
- package/dist/src/services/skill-bundle/generator.js +19 -0
- package/dist/src/services/skill-bundle/generator.js.map +1 -1
- package/dist/src/storage/interface.d.ts +48 -4
- package/dist/src/storage/interface.d.ts.map +1 -1
- package/dist/src/storage/interface.js.map +1 -1
- package/dist/src/storage/providers/mongodb/index.d.ts +31 -0
- package/dist/src/storage/providers/mongodb/index.d.ts.map +1 -1
- package/dist/src/storage/providers/mongodb/index.js +187 -9
- package/dist/src/storage/providers/mongodb/index.js.map +1 -1
- package/dist/src/storage/providers/sqlite/index.d.ts +31 -0
- package/dist/src/storage/providers/sqlite/index.d.ts.map +1 -1
- package/dist/src/storage/providers/sqlite/index.js +131 -8
- package/dist/src/storage/providers/sqlite/index.js.map +1 -1
- package/dist/src/storage/providers/sqlite/repos/agent-task.d.ts.map +1 -1
- package/dist/src/storage/providers/sqlite/repos/agent-task.js +6 -4
- package/dist/src/storage/providers/sqlite/repos/agent-task.js.map +1 -1
- package/dist/src/storage/providers/sqlite/schema.d.ts.map +1 -1
- package/dist/src/storage/providers/sqlite/schema.js +79 -8
- package/dist/src/storage/providers/sqlite/schema.js.map +1 -1
- package/dist/src/storage/repositories/pat.repository.d.ts +40 -0
- package/dist/src/storage/repositories/pat.repository.d.ts.map +1 -0
- package/dist/src/storage/repositories/pat.repository.js +14 -0
- package/dist/src/storage/repositories/pat.repository.js.map +1 -0
- package/dist/src/storage/repositories/scheduler.repository.d.ts +2 -0
- package/dist/src/storage/repositories/scheduler.repository.d.ts.map +1 -1
- package/dist/src/storage/repositories/session.repository.d.ts +51 -0
- package/dist/src/storage/repositories/session.repository.d.ts.map +1 -1
- package/dist/src/storage/repositories/session.repository.js +12 -0
- package/dist/src/storage/repositories/session.repository.js.map +1 -1
- package/dist/src/utils/env-config.d.ts.map +1 -1
- package/dist/src/utils/env-config.js +25 -1
- package/dist/src/utils/env-config.js.map +1 -1
- package/dist/src/utils/env-validator.d.ts.map +1 -1
- package/dist/src/utils/env-validator.js +4 -0
- package/dist/src/utils/env-validator.js.map +1 -1
- package/package.json +1 -1
- package/prisma/schema.prisma +48 -0
|
@@ -1,13 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
2
|
+
* @file portal.js
|
|
3
|
+
* @description Portal (Genesis 001) view module — main landing page with 3D
|
|
4
|
+
* canvas, live feed, mega-prompts, and expandable world.
|
|
5
|
+
* @version-history
|
|
6
|
+
* v1.1.0 — 2026-06-02 — Component unification (#11): live-feed header dot uses
|
|
7
|
+
* canonical <StatusDot status="live" /> instead of bespoke .live-dot.
|
|
4
8
|
*/
|
|
5
9
|
import { h } from 'preact';
|
|
6
10
|
import { useState, useEffect, useRef, useCallback } from 'preact/hooks';
|
|
7
11
|
import htm from 'htm';
|
|
8
12
|
import { t } from '/js/i18n.js';
|
|
9
|
-
import { escHtml
|
|
13
|
+
import { escHtml } from '/js/utils.js';
|
|
14
|
+
import { CopyButton as BaseCopyButton } from '/components/CopyButton.js';
|
|
10
15
|
import { useViewCSS } from '/components/useViewCSS.js';
|
|
16
|
+
import { StatusDot } from '/components/StatusDot.js';
|
|
11
17
|
|
|
12
18
|
const html = htm.bind(h);
|
|
13
19
|
const NODE_URL = window.location.origin;
|
|
@@ -990,7 +996,7 @@ function OnelinersFeed({ locale }) {
|
|
|
990
996
|
return html`
|
|
991
997
|
<div class="content-group">
|
|
992
998
|
<div class="oneliners-header">
|
|
993
|
-
|
|
999
|
+
<${StatusDot} status="live" />
|
|
994
1000
|
<span class="oneliners-label">${t('portal.oneliners.title')}</span>
|
|
995
1001
|
</div>
|
|
996
1002
|
<section class="provocation">
|
|
@@ -1016,21 +1022,10 @@ function OnelinersFeed({ locale }) {
|
|
|
1016
1022
|
/* ══════════════════════════════════════════════
|
|
1017
1023
|
COPY BUTTON COMPONENT
|
|
1018
1024
|
══════════════════════════════════════════════ */
|
|
1025
|
+
// Thin wrapper over the canonical CopyButton (aliased to avoid the name clash) \u2014
|
|
1026
|
+
// keeps the .copy-btn styling + the "<copied> \u2714" confirmed label.
|
|
1019
1027
|
function CopyButton({ text, label, copiedLabel }) {
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
const handleCopy = useCallback(() => {
|
|
1023
|
-
copyToClipboard(text).then(() => {
|
|
1024
|
-
setCopied(true);
|
|
1025
|
-
setTimeout(() => setCopied(false), 2000);
|
|
1026
|
-
});
|
|
1027
|
-
}, [text]);
|
|
1028
|
-
|
|
1029
|
-
return html`
|
|
1030
|
-
<button class="copy-btn ${copied ? 'copied' : ''}" type="button" onClick=${handleCopy}>
|
|
1031
|
-
${copied ? (copiedLabel || t('portal.prompt.copied')) + ' \u2714' : label}
|
|
1032
|
-
</button>
|
|
1033
|
-
`;
|
|
1028
|
+
return html`<${BaseCopyButton} text=${text} label=${label} copiedLabel=${(copiedLabel || t('portal.prompt.copied')) + ' \u2714'} className="copy-btn" />`;
|
|
1034
1029
|
}
|
|
1035
1030
|
|
|
1036
1031
|
/* ══════════════════════════════════════════════
|
|
@@ -1120,9 +1115,9 @@ function UserWorldAccordion({ navigate }) {
|
|
|
1120
1115
|
</div>
|
|
1121
1116
|
<div class="user-world-grid">
|
|
1122
1117
|
${statItems.map(([key, val]) => html`
|
|
1123
|
-
<div class="
|
|
1124
|
-
<div class="
|
|
1125
|
-
<div class="
|
|
1118
|
+
<div class="stat-card" key=${key}>
|
|
1119
|
+
<div class="stat-card-label">${t('portal.userWorld.stats.' + key)}</div>
|
|
1120
|
+
<div class="stat-card-value">${val}</div>
|
|
1126
1121
|
</div>
|
|
1127
1122
|
`)}
|
|
1128
1123
|
</div>
|
|
@@ -1,11 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file portfolio.js
|
|
3
|
+
* @description Portfolio view — builder (select content, generate AI prompt,
|
|
4
|
+
* upload HTML) and public viewer (sandboxed iframe render).
|
|
5
|
+
* @version-history
|
|
6
|
+
* v1.1.0 — 2026-06-02 — Migrate bespoke prompt-copy button to canonical CopyButton component
|
|
7
|
+
*/
|
|
1
8
|
import { h } from 'preact';
|
|
2
9
|
import { useState, useEffect, useCallback, useRef } from 'preact/hooks';
|
|
3
10
|
import htm from 'htm';
|
|
4
11
|
const html = htm.bind(h);
|
|
5
12
|
import { t } from '/js/i18n.js';
|
|
6
|
-
import { escHtml,
|
|
13
|
+
import { escHtml, handleImgError } from '/js/utils.js';
|
|
7
14
|
import { apiGet, apiPut } from '/js/api.js';
|
|
8
15
|
import TagCloud from '/js/components/tag-cloud.js';
|
|
16
|
+
import { CopyButton } from '/components/CopyButton.js';
|
|
9
17
|
|
|
10
18
|
const NODE_URL = typeof window !== 'undefined' ? window.location.origin : '';
|
|
11
19
|
|
|
@@ -238,7 +246,6 @@ function PortfolioBuilder({ session, navigate }) {
|
|
|
238
246
|
|
|
239
247
|
// Prompt
|
|
240
248
|
const [generatedPrompt, setGeneratedPrompt] = useState('');
|
|
241
|
-
const [promptCopied, setPromptCopied] = useState(false);
|
|
242
249
|
|
|
243
250
|
// Upload
|
|
244
251
|
const [uploading, setUploading] = useState(false);
|
|
@@ -313,13 +320,6 @@ function PortfolioBuilder({ session, navigate }) {
|
|
|
313
320
|
});
|
|
314
321
|
};
|
|
315
322
|
|
|
316
|
-
// Copy prompt
|
|
317
|
-
const handleCopyPrompt = async () => {
|
|
318
|
-
await copyToClipboard(generatedPrompt);
|
|
319
|
-
setPromptCopied(true);
|
|
320
|
-
setTimeout(() => setPromptCopied(false), 2000);
|
|
321
|
-
};
|
|
322
|
-
|
|
323
323
|
// Download prompt as .txt
|
|
324
324
|
const handleDownloadPrompt = () => {
|
|
325
325
|
const blob = new Blob([generatedPrompt], { type: 'text/plain' });
|
|
@@ -614,9 +614,8 @@ function PortfolioBuilder({ session, navigate }) {
|
|
|
614
614
|
${generatedPrompt && html`
|
|
615
615
|
<div class="portfolio-prompt-output">${generatedPrompt}</div>
|
|
616
616
|
<div class="portfolio-prompt-actions">
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
</button>
|
|
617
|
+
<${CopyButton} text=${generatedPrompt} className="btn-primary"
|
|
618
|
+
label=${t('portfolio.builder.copyPrompt')} copiedLabel=${t('portfolio.builder.promptCopied')} />
|
|
620
619
|
<button class="btn btn-ghost" onClick=${handleDownloadPrompt}>
|
|
621
620
|
${t('portfolio.builder.downloadPrompt')}
|
|
622
621
|
</button>
|
|
@@ -22,6 +22,7 @@ import { escHtml, copyToClipboard } from '/js/utils.js';
|
|
|
22
22
|
import { getNodeUrl } from '/js/services/auth.js';
|
|
23
23
|
import { apiGet, apiPost, apiDelete } from '/js/api.js';
|
|
24
24
|
import { useConfirm } from '/components/Modal.js';
|
|
25
|
+
import { CopyButton } from '/components/CopyButton.js';
|
|
25
26
|
import * as groupsApi from '/js/services/sharing-groups.js';
|
|
26
27
|
import { getOwnerDefaults, upsertOwnerDefaults } from '/js/services/agent-directives.js';
|
|
27
28
|
|
|
@@ -532,6 +533,247 @@ function AgentDefaultsSection({ showToast }) {
|
|
|
532
533
|
`;
|
|
533
534
|
}
|
|
534
535
|
|
|
536
|
+
/* ═══════════════════════════════════════════════════════════════════
|
|
537
|
+
Agent Access Tokens Section
|
|
538
|
+
═══════════════════════════════════════════════════════════════════ */
|
|
539
|
+
|
|
540
|
+
// Mirrors the agent scope domains (agents-tab.js) — the same list agents are granted from.
|
|
541
|
+
const PAT_SCOPE_DOMAINS = [
|
|
542
|
+
{ key: 'memory', permissions: ['read', 'write', 'delete'] },
|
|
543
|
+
{ key: 'work', permissions: ['request', 'read', 'accept', 'publish'] },
|
|
544
|
+
{ key: 'social', permissions: ['read', 'write'] },
|
|
545
|
+
{ key: 'wallet', permissions: ['read'] },
|
|
546
|
+
{ key: 'consent', permissions: ['manage'] },
|
|
547
|
+
{ key: 'catalogue', permissions: ['read'] },
|
|
548
|
+
{ key: 'generator', permissions: ['read', 'write', 'execute'] },
|
|
549
|
+
{ key: 'task', permissions: ['read', 'write', 'manage'] },
|
|
550
|
+
{ key: 'cortex', permissions: ['write'] },
|
|
551
|
+
{ key: 'ext', permissions: ['write'] },
|
|
552
|
+
];
|
|
553
|
+
|
|
554
|
+
function buildPatAgentPrompt(tokenData) {
|
|
555
|
+
const url = (typeof window !== 'undefined' ? window.location.origin : '');
|
|
556
|
+
const level = tokenData.grant_operator ? 'operator (full node control)'
|
|
557
|
+
: tokenData.grant_owner ? 'owner (acts as me)'
|
|
558
|
+
: `scoped agent (${(tokenData.scopes || []).join(', ') || 'no scopes'})`;
|
|
559
|
+
return `I'm giving you an AIMEAT access token so you can log in to my node (${url}) as ${level} and check that a web app works.
|
|
560
|
+
|
|
561
|
+
Use it by adding this HTTP header to your requests (and, for browser testing, set it as an extra header in your browser automation so every request the page makes carries it):
|
|
562
|
+
|
|
563
|
+
Authorization: Bearer ${tokenData.token}
|
|
564
|
+
|
|
565
|
+
The server recognizes the token and treats every request as if I'm logged in — no extra login step. Open the app, click through it, read/write via it, and confirm it behaves correctly. Tell me what you find.`;
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
function AccessTokensSection({ session, showToast }) {
|
|
569
|
+
const { confirm, ConfirmUI } = useConfirm();
|
|
570
|
+
const isOperator = (session.roles || []).includes('operator');
|
|
571
|
+
|
|
572
|
+
const [tokens, setTokens] = useState(null);
|
|
573
|
+
const [showCreate, setShowCreate] = useState(false);
|
|
574
|
+
const [creating, setCreating] = useState(false);
|
|
575
|
+
const [created, setCreated] = useState(null); // { token, prompt }
|
|
576
|
+
|
|
577
|
+
// Create form
|
|
578
|
+
const [label, setLabel] = useState('');
|
|
579
|
+
const [selScopes, setSelScopes] = useState({});
|
|
580
|
+
const [grantOwner, setGrantOwner] = useState(false);
|
|
581
|
+
const [grantOperator, setGrantOperator] = useState(false);
|
|
582
|
+
const [readOwnerData, setReadOwnerData] = useState(false);
|
|
583
|
+
const [expiry, setExpiry] = useState('');
|
|
584
|
+
|
|
585
|
+
const fullLevel = grantOwner || grantOperator;
|
|
586
|
+
|
|
587
|
+
const load = useCallback(async () => {
|
|
588
|
+
try { const r = await apiGet('/v1/access/tokens'); setTokens(r.data?.tokens || []); }
|
|
589
|
+
catch { setTokens([]); }
|
|
590
|
+
}, []);
|
|
591
|
+
useEffect(() => { load(); }, [load]);
|
|
592
|
+
|
|
593
|
+
const liveRef = useRef(load);
|
|
594
|
+
liveRef.current = load;
|
|
595
|
+
useEffect(() => {
|
|
596
|
+
const handler = () => liveRef.current();
|
|
597
|
+
window.addEventListener('aimeat-live-update', handler);
|
|
598
|
+
return () => window.removeEventListener('aimeat-live-update', handler);
|
|
599
|
+
}, []);
|
|
600
|
+
|
|
601
|
+
const resetForm = () => {
|
|
602
|
+
setShowCreate(false); setLabel(''); setSelScopes({});
|
|
603
|
+
setGrantOwner(false); setGrantOperator(false); setReadOwnerData(false); setExpiry('');
|
|
604
|
+
};
|
|
605
|
+
|
|
606
|
+
const toggleScope = (s) => setSelScopes(prev => ({ ...prev, [s]: !prev[s] }));
|
|
607
|
+
|
|
608
|
+
const handleCreate = useCallback(async () => {
|
|
609
|
+
if (!label.trim()) { showToast(t('profile.access.patLabelRequired') || 'Give the token a name'); return; }
|
|
610
|
+
const scopes = fullLevel ? [] : Object.keys(selScopes).filter(k => selScopes[k]);
|
|
611
|
+
if (!fullLevel && scopes.length === 0) {
|
|
612
|
+
showToast(t('profile.access.patScopeRequired') || 'Select at least one scope, or Full / Operator');
|
|
613
|
+
return;
|
|
614
|
+
}
|
|
615
|
+
setCreating(true);
|
|
616
|
+
try {
|
|
617
|
+
const body = {
|
|
618
|
+
label: label.trim(), scopes,
|
|
619
|
+
grant_owner: grantOwner, grant_operator: grantOperator, read_owner_data: readOwnerData,
|
|
620
|
+
};
|
|
621
|
+
if (expiry) body.expires_in = parseInt(expiry, 10);
|
|
622
|
+
const r = await apiPost('/v1/access/tokens', body);
|
|
623
|
+
const data = r.data;
|
|
624
|
+
setCreated({ token: data.token, prompt: buildPatAgentPrompt(data) });
|
|
625
|
+
resetForm();
|
|
626
|
+
load();
|
|
627
|
+
} catch (e) {
|
|
628
|
+
showToast(e.message || (t('profile.access.patCreateError') || 'Failed to create token'));
|
|
629
|
+
} finally { setCreating(false); }
|
|
630
|
+
}, [label, fullLevel, selScopes, grantOwner, grantOperator, readOwnerData, expiry, showToast, load]);
|
|
631
|
+
|
|
632
|
+
const handleRevoke = useCallback((id, lbl) => {
|
|
633
|
+
confirm(
|
|
634
|
+
(t('profile.access.patConfirmRevoke') || 'Revoke token "{name}"? Agents using it lose access immediately.').replace('{name}', lbl),
|
|
635
|
+
async () => {
|
|
636
|
+
try {
|
|
637
|
+
await apiDelete('/v1/access/tokens/' + id);
|
|
638
|
+
showToast(t('profile.access.patRevoked') || 'Token revoked');
|
|
639
|
+
load();
|
|
640
|
+
} catch (e) { showToast(e.message); }
|
|
641
|
+
},
|
|
642
|
+
{ danger: true },
|
|
643
|
+
);
|
|
644
|
+
}, [confirm, showToast, load]);
|
|
645
|
+
|
|
646
|
+
const levelBadge = (tok) => {
|
|
647
|
+
if (tok.grant_operator) return html`<span class="badge badge-danger">${t('profile.access.patOperator') || 'Operator'}</span>`;
|
|
648
|
+
if (tok.grant_owner) return html`<span class="badge badge-warn">${t('profile.access.patFullOwner') || 'Full owner'}</span>`;
|
|
649
|
+
return html`<span class="badge badge-info">${(tok.scopes || []).length} ${t('profile.access.patScopes') || 'scopes'}</span>`;
|
|
650
|
+
};
|
|
651
|
+
|
|
652
|
+
return html`
|
|
653
|
+
<h3 class="card-h3 mt-section">\u{1F511} ${t('profile.access.patTitle') || 'Agent Access Tokens'}</h3>
|
|
654
|
+
<div class="section-desc">${t('profile.access.patDesc') || 'Create a revocable token an agent can use (as a Bearer header) to log in and test your apps. One token can be shared across all your agents.'}</div>
|
|
655
|
+
|
|
656
|
+
${created && html`
|
|
657
|
+
<div class="card access-card-warn">
|
|
658
|
+
<div class="card-title">${t('profile.access.patCreatedTitle') || 'Token created — copy it now'}</div>
|
|
659
|
+
<div class="access-warn-text">⚠ ${t('profile.access.patShownOnce') || 'This token is shown only once. Store it now; you cannot see it again.'}</div>
|
|
660
|
+
<div class="mem-item">
|
|
661
|
+
<div class="access-mono access-pat-token">${escHtml(created.token)}</div>
|
|
662
|
+
<${CopyButton} text=${created.token} className="btn-sm"
|
|
663
|
+
onCopied=${() => showToast(t('profile.access.patCopied') || 'Token copied')} />
|
|
664
|
+
</div>
|
|
665
|
+
<div class="mem-item access-pat-prompt-row">
|
|
666
|
+
<span class="mem-key">${t('profile.access.patAgentPrompt') || 'Ready-made agent prompt'}</span>
|
|
667
|
+
<${CopyButton} text=${created.prompt} className="btn-sm"
|
|
668
|
+
onCopied=${() => showToast(t('profile.access.patPromptCopied') || 'Prompt copied')} />
|
|
669
|
+
</div>
|
|
670
|
+
<div class="form-actions">
|
|
671
|
+
<button class="btn-ghost btn-sm" onClick=${() => setCreated(null)}>${t('profile.access.patDone') || 'Done'}</button>
|
|
672
|
+
</div>
|
|
673
|
+
</div>
|
|
674
|
+
`}
|
|
675
|
+
|
|
676
|
+
${tokens === null
|
|
677
|
+
? html`<div class="empty">${t('profile.access.patLoading') || 'Loading...'}</div>`
|
|
678
|
+
: tokens.length === 0
|
|
679
|
+
? html`<div class="empty">${t('profile.access.patEmpty') || 'No access tokens yet.'}</div>`
|
|
680
|
+
: tokens.map(tok => html`
|
|
681
|
+
<div class="card" key=${tok.id}>
|
|
682
|
+
<div class="flex-between">
|
|
683
|
+
<div class="card-title">${escHtml(tok.label)}</div>
|
|
684
|
+
${levelBadge(tok)}
|
|
685
|
+
</div>
|
|
686
|
+
<div class="detail-grid">
|
|
687
|
+
<div class="detail-item"><span class="detail-label">${t('profile.access.patCreatedAt') || 'Created'}</span><span class="detail-value">${new Date(tok.created_at).toLocaleDateString()}</span></div>
|
|
688
|
+
<div class="detail-item"><span class="detail-label">${t('profile.access.patLastUsed') || 'Last used'}</span><span class="detail-value">${tok.last_used_at ? new Date(tok.last_used_at).toLocaleString() : (t('profile.access.patNever') || 'never')}</span></div>
|
|
689
|
+
<div class="detail-item"><span class="detail-label">${t('profile.access.patExpires') || 'Expires'}</span><span class="detail-value">${tok.expires_at ? new Date(tok.expires_at).toLocaleDateString() : (t('profile.access.patNoExpiry') || 'never')}</span></div>
|
|
690
|
+
</div>
|
|
691
|
+
${!tok.grant_owner && !tok.grant_operator && (tok.scopes || []).length > 0 && html`
|
|
692
|
+
<div class="flex-row-wrap mt-half">
|
|
693
|
+
${tok.scopes.map(s => html`<span class="badge badge-muted" key=${s}>${s}</span>`)}
|
|
694
|
+
</div>
|
|
695
|
+
`}
|
|
696
|
+
<div class="card-actions">
|
|
697
|
+
<button class="btn-danger-solid btn-sm" onClick=${() => handleRevoke(tok.id, tok.label)}>${t('profile.access.patRevoke') || 'Revoke'}</button>
|
|
698
|
+
</div>
|
|
699
|
+
</div>
|
|
700
|
+
`)
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
${!showCreate ? html`
|
|
704
|
+
<div class="mb-1"><button class="btn-primary" onClick=${() => setShowCreate(true)}>${t('profile.access.patCreate') || 'New Token'}</button></div>
|
|
705
|
+
` : html`
|
|
706
|
+
<div class="create-form">
|
|
707
|
+
<h4 class="card-h3 mb-half">${t('profile.access.patCreateTitle') || 'Create Access Token'}</h4>
|
|
708
|
+
<div class="flex-col">
|
|
709
|
+
<div class="form-row">
|
|
710
|
+
<label>${t('profile.access.patLabel') || 'Name'}</label>
|
|
711
|
+
<input type="text" class="input-field input-sm"
|
|
712
|
+
placeholder=${t('profile.access.patLabelPlaceholder') || 'e.g. App tester'}
|
|
713
|
+
value=${label} onInput=${e => setLabel(e.target.value)} />
|
|
714
|
+
</div>
|
|
715
|
+
|
|
716
|
+
<div class="form-row">
|
|
717
|
+
<label>${t('profile.access.patLevel') || 'Permissions'}</label>
|
|
718
|
+
<label class="flex-row">
|
|
719
|
+
<input type="checkbox" checked=${grantOwner} disabled=${grantOperator}
|
|
720
|
+
onChange=${() => setGrantOwner(v => !v)} />
|
|
721
|
+
<strong>${t('profile.access.patFullOwner') || 'Full owner'}</strong> — ${t('profile.access.patFullOwnerHint') || 'acts as you; sees all apps & data'}
|
|
722
|
+
</label>
|
|
723
|
+
${isOperator && html`
|
|
724
|
+
<label class="flex-row">
|
|
725
|
+
<input type="checkbox" checked=${grantOperator}
|
|
726
|
+
onChange=${() => setGrantOperator(v => !v)} />
|
|
727
|
+
<strong>${t('profile.access.patOperator') || 'Operator'}</strong> — ${t('profile.access.patOperatorHint') || 'full node administration (master key)'}
|
|
728
|
+
</label>
|
|
729
|
+
`}
|
|
730
|
+
${fullLevel && html`<span class="badge badge-warn">⚠ ${t('profile.access.patMasterKeyWarn') || 'Master key — guard it like a password'}</span>`}
|
|
731
|
+
</div>
|
|
732
|
+
|
|
733
|
+
${!fullLevel && html`
|
|
734
|
+
<div class="form-row">
|
|
735
|
+
<label>${t('profile.access.patSelectScopes') || 'Scopes'}</label>
|
|
736
|
+
${PAT_SCOPE_DOMAINS.map(dom => html`
|
|
737
|
+
<div class="flex-row-wrap access-pat-scope-row" key=${dom.key}>
|
|
738
|
+
<span class="access-pat-scope-domain">${dom.key}</span>
|
|
739
|
+
${dom.permissions.map(p => {
|
|
740
|
+
const s = `${dom.key}:${p}`;
|
|
741
|
+
return html`<label class="flex-row" key=${s}>
|
|
742
|
+
<input type="checkbox" checked=${!!selScopes[s]} onChange=${() => toggleScope(s)} /> ${p}
|
|
743
|
+
</label>`;
|
|
744
|
+
})}
|
|
745
|
+
</div>
|
|
746
|
+
`)}
|
|
747
|
+
<label class="flex-row mt-half">
|
|
748
|
+
<input type="checkbox" checked=${readOwnerData} onChange=${() => setReadOwnerData(v => !v)} />
|
|
749
|
+
${t('profile.access.patReadOwnerData') || 'Also read my data (otherwise a clean sandbox)'}
|
|
750
|
+
</label>
|
|
751
|
+
</div>
|
|
752
|
+
`}
|
|
753
|
+
|
|
754
|
+
<div class="form-row">
|
|
755
|
+
<label>${t('profile.access.patExpiry') || 'Expiry'}</label>
|
|
756
|
+
<select class="input-field input-sm" value=${expiry} onChange=${e => setExpiry(e.target.value)}>
|
|
757
|
+
<option value="">${t('profile.access.patNoExpiry') || 'No expiry'}</option>
|
|
758
|
+
<option value="86400">${t('profile.access.patExpiry1d') || '24 hours'}</option>
|
|
759
|
+
<option value="604800">${t('profile.access.patExpiry7d') || '7 days'}</option>
|
|
760
|
+
<option value="2592000">${t('profile.access.patExpiry30d') || '30 days'}</option>
|
|
761
|
+
</select>
|
|
762
|
+
</div>
|
|
763
|
+
|
|
764
|
+
<div class="form-actions">
|
|
765
|
+
<button class="btn-primary btn-sm" onClick=${handleCreate} disabled=${creating}>
|
|
766
|
+
${creating ? '...' : (t('profile.access.patCreateBtn') || 'Create Token')}
|
|
767
|
+
</button>
|
|
768
|
+
<button class="btn-ghost btn-sm" onClick=${resetForm}>${t('profile.access.patCancel') || 'Cancel'}</button>
|
|
769
|
+
</div>
|
|
770
|
+
</div>
|
|
771
|
+
</div>
|
|
772
|
+
`}
|
|
773
|
+
<${ConfirmUI} />
|
|
774
|
+
`;
|
|
775
|
+
}
|
|
776
|
+
|
|
535
777
|
/* ═══════════════════════════════════════════════════════════════════
|
|
536
778
|
Main Access Tab
|
|
537
779
|
═══════════════════════════════════════════════════════════════════ */
|
|
@@ -627,7 +869,13 @@ export default function AccessTab({ session, showToast }) {
|
|
|
627
869
|
onMouseEnter=${() => setKeyBlurred(false)} onMouseLeave=${() => setKeyBlurred(true)}>
|
|
628
870
|
${escHtml(ownerKey)}
|
|
629
871
|
</div>
|
|
630
|
-
<
|
|
872
|
+
<div class="flex-row">
|
|
873
|
+
<span class="badge badge-warn">${t('profile.access.hoverReveal')}</span>
|
|
874
|
+
<span onClick=${(e) => e.stopPropagation()}>
|
|
875
|
+
<${CopyButton} text=${ownerKey} className="btn-sm"
|
|
876
|
+
onCopied=${() => showToast(t('profile.access.keyCopied'))} />
|
|
877
|
+
</span>
|
|
878
|
+
</div>
|
|
631
879
|
</div>
|
|
632
880
|
<div class="access-warn-text">⚠ ${t('profile.access.keepSafe')}</div>
|
|
633
881
|
</div>
|
|
@@ -678,6 +926,7 @@ export default function AccessTab({ session, showToast }) {
|
|
|
678
926
|
</div>
|
|
679
927
|
</div>
|
|
680
928
|
|
|
929
|
+
<${AccessTokensSection} session=${session} showToast=${showToast} />
|
|
681
930
|
<${SharingGroupsSection} showToast=${showToast} />
|
|
682
931
|
<${AgentDefaultsSection} showToast=${showToast} />
|
|
683
932
|
`;
|
|
@@ -3,6 +3,20 @@
|
|
|
3
3
|
* @description Agent card component with collapsed/expanded states,
|
|
4
4
|
* Two-Zone Header (identity + state-dependent status), and tab bar.
|
|
5
5
|
* @version-history
|
|
6
|
+
* v1.12.0 -- 2026-06-02 -- Active tracked tab never shows its own badge and is
|
|
7
|
+
* kept marked-seen as new items arrive (so a message landing while you're on
|
|
8
|
+
* the Messages tab doesn't raise a (1) you're already looking at).
|
|
9
|
+
* v1.11.0 -- 2026-06-02 -- Unseen-change badges: collapsed-card mini-badge
|
|
10
|
+
* (total new across Tasks/Messages/Memory) + per-tab number badges on the
|
|
11
|
+
* Tasks/Messages/Memory tab buttons; opening a tracked tab clears its badge
|
|
12
|
+
* (via onTabSeen). Counts come from the `changes` prop (localStorage
|
|
13
|
+
* last-seen baseline computed in agents-tab.js).
|
|
14
|
+
* v1.10.0 -- 2026-06-01 -- Reorder TABS to owner-requested order: Integration,
|
|
15
|
+
* Tasks, Messages, Data Access, Quality, Activity, then Directives /
|
|
16
|
+
* Agent Config / Services (README stays prepended/first).
|
|
17
|
+
* v1.9.0 -- 2026-06-01 -- Reorder TABS by owner usage frequency (Integration,
|
|
18
|
+
* Tasks, Data Access, Quality, Activity, ... setup tabs last) so the
|
|
19
|
+
* now-wrapping tab bar keeps the most-used tabs on the top row.
|
|
6
20
|
* v1.8.0 -- 2026-05-31 -- Add Quality tab (per-context reviews + performance + custom stats)
|
|
7
21
|
* v1.0.0 -- 2026-05-24 -- Initial creation for Agent Detail Tab-View
|
|
8
22
|
* v1.1.0 -- 2026-05-24 -- Fix C6: remove GAII from Zone 1, M1: add Next prefix, C1: production stats, C2: problem action buttons
|
|
@@ -37,6 +51,7 @@ import TabDataAccess from './tab-data-access.js';
|
|
|
37
51
|
import TabDirectives from './tab-directives.js';
|
|
38
52
|
import TabAgentConfig from './tab-agent-config.js';
|
|
39
53
|
import TabActivity from './tab-activity.js';
|
|
54
|
+
import TabSchedules from './tab-schedules.js';
|
|
40
55
|
import TabQuality from './tab-quality.js';
|
|
41
56
|
import TabServices from './tab-services.js';
|
|
42
57
|
|
|
@@ -44,15 +59,23 @@ const html = htm.bind(h);
|
|
|
44
59
|
|
|
45
60
|
// README is opt-in and prepended only when the agent has published one, so it
|
|
46
61
|
// is NOT in this base list. See readmeTab below.
|
|
62
|
+
//
|
|
63
|
+
// Explicit owner-requested order (not auto-grouping). The tab bar wraps to a
|
|
64
|
+
// second row (flex-wrap, see .pf-agd-tabs CSS) instead of scrolling, so the
|
|
65
|
+
// first-listed tabs naturally land on the top row. README is prepended
|
|
66
|
+
// separately when present, so the effective order is:
|
|
67
|
+
// README · Integration · Tasks · Messages · Data Access · Quality · Activity
|
|
68
|
+
// · Directives · Agent Config · Services
|
|
47
69
|
const TABS = [
|
|
48
70
|
{ id: 'integration', key: 'profile.agents.detail.tabs.integration' },
|
|
49
71
|
{ id: 'tasks', key: 'profile.agents.detail.tabs.tasks' },
|
|
50
72
|
{ id: 'messages', key: 'profile.agents.detail.tabs.messages' },
|
|
51
73
|
{ id: 'data-access', key: 'profile.agents.detail.tabs.data_access' },
|
|
74
|
+
{ id: 'quality', key: 'profile.agents.detail.tabs.quality' },
|
|
75
|
+
{ id: 'activity', key: 'profile.agents.detail.tabs.activity' },
|
|
76
|
+
{ id: 'schedules', key: 'profile.agents.detail.tabs.schedules' },
|
|
52
77
|
{ id: 'directives', key: 'profile.agents.detail.tabs.directives' },
|
|
53
78
|
{ id: 'agent-config', key: 'profile.agents.detail.tabs.agent_config' },
|
|
54
|
-
{ id: 'activity', key: 'profile.agents.detail.tabs.activity' },
|
|
55
|
-
{ id: 'quality', key: 'profile.agents.detail.tabs.quality' },
|
|
56
79
|
{ id: 'services', key: 'profile.agents.detail.tabs.services' },
|
|
57
80
|
];
|
|
58
81
|
|
|
@@ -78,7 +101,17 @@ async function fetchReadme(agent) {
|
|
|
78
101
|
}
|
|
79
102
|
}
|
|
80
103
|
|
|
81
|
-
|
|
104
|
+
// Maps a tab id to its change-count key in the `changes` prop. Only these three
|
|
105
|
+
// tabs carry unseen-change badges (Tasks, Messages, Memory/data-access).
|
|
106
|
+
const TAB_CHANGE_KEY = { tasks: 'tasks', messages: 'messages', 'data-access': 'memory' };
|
|
107
|
+
|
|
108
|
+
// Total unseen changes across the three tracked tabs (collapsed mini-badge).
|
|
109
|
+
function totalChanges(changes) {
|
|
110
|
+
if (!changes) return 0;
|
|
111
|
+
return (changes.tasks || 0) + (changes.messages || 0) + (changes.memory || 0);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export default function AgentCard({ agent, onboarding, expanded, onToggle, session, showToast, allAgents, changes, onTabSeen, onScopesClick, onDeleteClick, onFederateToggle, onPopOut, soloMode = false }) {
|
|
82
115
|
const state = detectAgentState(agent, onboarding);
|
|
83
116
|
const [activeTab, setActiveTab] = useState(null);
|
|
84
117
|
// README markdown: undefined = not loaded, '' = none published, string = show tab.
|
|
@@ -108,6 +141,16 @@ export default function AgentCard({ agent, onboarding, expanded, onToggle, sessi
|
|
|
108
141
|
setActiveTab(hasReadme ? 'readme' : getDefaultTab(state));
|
|
109
142
|
}, [expanded, readme]);
|
|
110
143
|
|
|
144
|
+
// While the owner is actively viewing a tracked tab (Tasks/Messages/Memory),
|
|
145
|
+
// keep it marked-seen as new items arrive. Without this, a message landing
|
|
146
|
+
// while you're on the Messages tab would raise its (1) badge even though you
|
|
147
|
+
// are looking right at it. Non-active tabs still accumulate their badge.
|
|
148
|
+
useEffect(() => {
|
|
149
|
+
if (!expanded) return;
|
|
150
|
+
const key = TAB_CHANGE_KEY[activeTab];
|
|
151
|
+
if (key && onTabSeen && changes?.[key]) onTabSeen(agent.name, key);
|
|
152
|
+
}, [expanded, activeTab, changes, agent.name]);
|
|
153
|
+
|
|
111
154
|
const handleCollapse = useCallback(() => {
|
|
112
155
|
// In solo mode the card is the whole window — clicking the header must not
|
|
113
156
|
// collapse it (there is nothing to collapse back into).
|
|
@@ -125,6 +168,7 @@ export default function AgentCard({ agent, onboarding, expanded, onToggle, sessi
|
|
|
125
168
|
<span class="pf-agd-expand-icon">▶</span>
|
|
126
169
|
<span class="pf-agd-collapsed-icon">🤖</span>
|
|
127
170
|
<span class="pf-agd-collapsed-name">${agent.display_name || agent.name}</span>
|
|
171
|
+
${renderChangeBadge(changes)}
|
|
128
172
|
<div class="pf-agd-collapsed-badges">
|
|
129
173
|
${renderModeBadge(agent)}
|
|
130
174
|
${renderPlatformBadge(onboarding)}
|
|
@@ -189,11 +233,22 @@ export default function AgentCard({ agent, onboarding, expanded, onToggle, sessi
|
|
|
189
233
|
<div class="pf-agd-tabs">
|
|
190
234
|
${tabs.map(tab => {
|
|
191
235
|
const label = t(tab.key);
|
|
236
|
+
const changeKey = TAB_CHANGE_KEY[tab.id];
|
|
237
|
+
// The tab you're currently viewing never shows a badge — you're
|
|
238
|
+
// looking at it (and the effect above keeps it marked-seen).
|
|
239
|
+
const count = (changeKey && activeTab !== tab.id) ? (changes?.[changeKey] || 0) : 0;
|
|
192
240
|
return html`
|
|
193
241
|
<button key=${tab.id}
|
|
194
242
|
class="pf-agd-tab ${activeTab === tab.id ? 'pf-agd-tab--active' : ''}"
|
|
195
|
-
onClick=${(e) => {
|
|
243
|
+
onClick=${(e) => {
|
|
244
|
+
e.stopPropagation();
|
|
245
|
+
userPickedTab.current = true;
|
|
246
|
+
setActiveTab(tab.id);
|
|
247
|
+
// Opening a tracked tab clears its unseen badge.
|
|
248
|
+
if (changeKey && onTabSeen) onTabSeen(agent.name, changeKey);
|
|
249
|
+
}}>
|
|
196
250
|
${label !== tab.key ? label : tab.id.charAt(0).toUpperCase() + tab.id.slice(1)}
|
|
251
|
+
${count > 0 ? html`<span class="pf-agd-tab-badge">${count > 99 ? '99+' : count}</span>` : ''}
|
|
197
252
|
</button>
|
|
198
253
|
`;
|
|
199
254
|
})}
|
|
@@ -239,6 +294,21 @@ function renderPopOut(onPopOut, agent) {
|
|
|
239
294
|
onClick=${(e) => { e.stopPropagation(); onPopOut(agent); }}>⤢</button>`;
|
|
240
295
|
}
|
|
241
296
|
|
|
297
|
+
// Collapsed-card mini-badge: total unseen changes across Tasks/Messages/Memory,
|
|
298
|
+
// so the owner can spot at a glance which agent has something new. Hidden when
|
|
299
|
+
// nothing changed; naturally absent from the expanded view (which does not
|
|
300
|
+
// render the collapsed bar). The title spells out the per-tab breakdown.
|
|
301
|
+
function renderChangeBadge(changes) {
|
|
302
|
+
const total = totalChanges(changes);
|
|
303
|
+
if (total <= 0) return null;
|
|
304
|
+
const parts = [];
|
|
305
|
+
if (changes.tasks) parts.push(`${t('profile.agents.detail.tabs.tasks')}: ${changes.tasks}`);
|
|
306
|
+
if (changes.messages) parts.push(`${t('profile.agents.detail.tabs.messages')}: ${changes.messages}`);
|
|
307
|
+
if (changes.memory) parts.push(`${t('profile.agents.detail.tabs.data_access')}: ${changes.memory}`);
|
|
308
|
+
const title = `${t('profile.agents.detail.changes.title')} — ${parts.join(', ')}`;
|
|
309
|
+
return html`<span class="pf-agd-change-badge" title=${title}>${total > 99 ? '99+' : total}</span>`;
|
|
310
|
+
}
|
|
311
|
+
|
|
242
312
|
function renderDeliveryIndicator(agent) {
|
|
243
313
|
const hasWebhook = agent.webhookUrl || agent.webhook_url;
|
|
244
314
|
const hasMcp = agent.mcpEnabled || agent.mcp_enabled;
|
|
@@ -491,6 +561,7 @@ function renderTabContent(activeTab, agent, onboarding, session, showToast, allA
|
|
|
491
561
|
case 'directives': return html`<${TabDirectives} ...${props} />`;
|
|
492
562
|
case 'agent-config': return html`<${TabAgentConfig} ...${props} />`;
|
|
493
563
|
case 'activity': return html`<${TabActivity} ...${props} />`;
|
|
564
|
+
case 'schedules': return html`<${TabSchedules} ...${props} />`;
|
|
494
565
|
case 'quality': return html`<${TabQuality} ...${props} />`;
|
|
495
566
|
case 'services': return html`<${TabServices} ...${props} />`;
|
|
496
567
|
default: return null;
|
|
@@ -53,8 +53,8 @@ export default function TabActivity({ agent, agentName, session, showToast }) {
|
|
|
53
53
|
const [logPage, setLogPage] = useState(1);
|
|
54
54
|
const [hasMore, setHasMore] = useState(false);
|
|
55
55
|
|
|
56
|
-
async function loadData() {
|
|
57
|
-
setLoading(true);
|
|
56
|
+
async function loadData({ showSpinner = true } = {}) {
|
|
57
|
+
if (showSpinner) setLoading(true);
|
|
58
58
|
try {
|
|
59
59
|
const [actResp, logResp, dirResp, whResp, telResp] = await Promise.all([
|
|
60
60
|
getActivity(agentName, 30).catch(() => null),
|
|
@@ -113,7 +113,9 @@ export default function TabActivity({ agent, agentName, session, showToast }) {
|
|
|
113
113
|
const loadRef = useRef(loadData);
|
|
114
114
|
loadRef.current = loadData;
|
|
115
115
|
useEffect(() => {
|
|
116
|
-
|
|
116
|
+
// Live-update refetch must NOT show the spinner -- it fires often and would
|
|
117
|
+
// flash the whole tab blank. First mount still shows the spinner.
|
|
118
|
+
const handler = () => loadRef.current({ showSpinner: false });
|
|
117
119
|
window.addEventListener('aimeat-live-update', handler);
|
|
118
120
|
return () => window.removeEventListener('aimeat-live-update', handler);
|
|
119
121
|
}, []);
|
|
@@ -139,18 +141,18 @@ export default function TabActivity({ agent, agentName, session, showToast }) {
|
|
|
139
141
|
<div>
|
|
140
142
|
<!-- Stats summary -->
|
|
141
143
|
${stats && html`
|
|
142
|
-
<div class="
|
|
143
|
-
<div class="
|
|
144
|
-
<div class="
|
|
145
|
-
<div class="
|
|
144
|
+
<div class="stat-grid">
|
|
145
|
+
<div class="stat-card">
|
|
146
|
+
<div class="stat-card-value">${stats.tasksCompleted ?? 0}</div>
|
|
147
|
+
<div class="stat-card-label">${t('profile.agents.activity.tasksCompleted')}</div>
|
|
146
148
|
</div>
|
|
147
|
-
<div class="
|
|
148
|
-
<div class="
|
|
149
|
-
<div class="
|
|
149
|
+
<div class="stat-card">
|
|
150
|
+
<div class="stat-card-value">${stats.tokensUsed30d ?? 0}</div>
|
|
151
|
+
<div class="stat-card-label">${t('profile.agents.activity.tokensUsed')}</div>
|
|
150
152
|
</div>
|
|
151
|
-
<div class="
|
|
152
|
-
<div class="
|
|
153
|
-
<div class="
|
|
153
|
+
<div class="stat-card">
|
|
154
|
+
<div class="stat-card-value">${stats.successRate != null ? `${Math.round(stats.successRate)}%` : '-'}</div>
|
|
155
|
+
<div class="stat-card-label">${t('profile.agents.activity.successRate')}</div>
|
|
154
156
|
</div>
|
|
155
157
|
</div>
|
|
156
158
|
`}
|