aimeat 1.23.2 → 1.23.3
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/locales/en.json +536 -21
- package/dist/locales/fi.json +548 -33
- package/dist/public/components/ContactCard.js +28 -0
- package/dist/public/css/views/admin.css +12 -0
- package/dist/public/css/views/agents-detail.css +62 -10
- package/dist/public/css/views/landing.css +87 -0
- package/dist/public/css/views/offers.css +96 -0
- package/dist/public/css/views/portfolio.css +18 -0
- package/dist/public/css/views/profile.css +153 -8
- package/dist/public/css/views/start.css +96 -0
- package/dist/public/img/business-hero.png +0 -0
- package/dist/public/img/toimitus.png +0 -0
- package/dist/public/js/components/tag-cloud.js +46 -31
- package/dist/public/js/components/tag-editor.js +2 -2
- package/dist/public/js/recents.js +40 -0
- package/dist/public/js/services/admin.js +6 -0
- package/dist/public/js/services/agents.js +34 -0
- package/dist/public/js/services/offers.js +152 -0
- package/dist/public/js/services/packages.js +1 -0
- package/dist/public/spa.html +32 -2
- package/dist/public/views/admin/subdomains-tab.js +182 -0
- package/dist/public/views/admin.js +2 -0
- package/dist/public/views/business.js +119 -0
- package/dist/public/views/how-it-works.js +159 -0
- package/dist/public/views/landing.js +235 -0
- package/dist/public/views/portfolio.js +120 -23
- package/dist/public/views/pricing.js +75 -0
- package/dist/public/views/profile/access-tab.js +124 -51
- package/dist/public/views/profile/agents/agent-card.js +68 -37
- package/dist/public/views/profile/agents/shared-board.js +56 -22
- package/dist/public/views/profile/agents/tab-activity.js +24 -12
- package/dist/public/views/profile/agents/tab-agent-config.js +31 -1
- package/dist/public/views/profile/agents/tab-data-access.js +9 -13
- package/dist/public/views/profile/agents/tab-integration.js +27 -1
- package/dist/public/views/profile/agents/tab-messages.js +6 -11
- package/dist/public/views/profile/agents/tab-quality.js +45 -5
- package/dist/public/views/profile/agents-tab.js +41 -6
- package/dist/public/views/profile/apps-tab.js +7 -1
- package/dist/public/views/profile/knowledge-tab.js +4 -1
- package/dist/public/views/profile/landing-page.js +427 -123
- package/dist/public/views/profile/memory-tab.js +409 -211
- package/dist/public/views/profile/nodes-tab.js +18 -3
- package/dist/public/views/profile/offers-tab.js +310 -0
- package/dist/public/views/profile/organisms-tab.js +289 -28
- package/dist/public/views/profile/packages-tab.js +152 -16
- package/dist/public/views/profile/portfolio-tab.js +98 -31
- package/dist/public/views/profile.js +2 -0
- package/dist/public/views/start-flows.js +281 -0
- package/dist/public/views/start.js +258 -0
- package/dist/src/generated/api-types.d.ts +856 -17
- package/dist/src/generated/api-types.d.ts.map +1 -1
- package/dist/src/generated/prisma-postgres/edge.js +15 -3
- package/dist/src/generated/prisma-postgres/index-browser.js +12 -0
- package/dist/src/generated/prisma-postgres/index.d.ts +1476 -160
- package/dist/src/generated/prisma-postgres/index.js +15 -3
- package/dist/src/generated/prisma-postgres/package.json +1 -1
- package/dist/src/generated/prisma-postgres/schema.prisma +11 -0
- package/dist/src/generated/prisma-postgres/wasm.js +15 -3
- package/dist/src/middleware/subdomain.d.ts +28 -0
- package/dist/src/middleware/subdomain.d.ts.map +1 -0
- package/dist/src/middleware/subdomain.js +32 -0
- package/dist/src/middleware/subdomain.js.map +1 -0
- package/dist/src/models/offer-schemas.d.ts +229 -0
- package/dist/src/models/offer-schemas.d.ts.map +1 -0
- package/dist/src/models/offer-schemas.js +100 -0
- package/dist/src/models/offer-schemas.js.map +1 -0
- package/dist/src/routes/agent-tasks.d.ts.map +1 -1
- package/dist/src/routes/agent-tasks.js +33 -0
- 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 +146 -1
- package/dist/src/routes/agents.js.map +1 -1
- package/dist/src/routes/apps-backup.d.ts +20 -0
- package/dist/src/routes/apps-backup.d.ts.map +1 -0
- package/dist/src/routes/apps-backup.js +165 -0
- package/dist/src/routes/apps-backup.js.map +1 -0
- package/dist/src/routes/ghii.d.ts.map +1 -1
- package/dist/src/routes/ghii.js +4 -0
- package/dist/src/routes/ghii.js.map +1 -1
- package/dist/src/routes/instances.d.ts +2 -1
- package/dist/src/routes/instances.d.ts.map +1 -1
- package/dist/src/routes/instances.js +59 -1
- package/dist/src/routes/instances.js.map +1 -1
- package/dist/src/routes/packages.d.ts.map +1 -1
- package/dist/src/routes/packages.js +32 -0
- package/dist/src/routes/packages.js.map +1 -1
- package/dist/src/routes/permissions.d.ts +12 -0
- package/dist/src/routes/permissions.d.ts.map +1 -1
- package/dist/src/routes/permissions.js +35 -3
- package/dist/src/routes/permissions.js.map +1 -1
- package/dist/src/routes/portal.d.ts +3 -0
- package/dist/src/routes/portal.d.ts.map +1 -1
- package/dist/src/routes/portal.js +12 -0
- package/dist/src/routes/portal.js.map +1 -1
- package/dist/src/routes/public-stats.d.ts +15 -0
- package/dist/src/routes/public-stats.d.ts.map +1 -0
- package/dist/src/routes/public-stats.js +90 -0
- package/dist/src/routes/public-stats.js.map +1 -0
- package/dist/src/routes/sse.d.ts +7 -2
- package/dist/src/routes/sse.d.ts.map +1 -1
- package/dist/src/routes/sse.js +35 -5
- package/dist/src/routes/sse.js.map +1 -1
- package/dist/src/routes/subdomains.d.ts +33 -0
- package/dist/src/routes/subdomains.d.ts.map +1 -0
- package/dist/src/routes/subdomains.js +208 -0
- package/dist/src/routes/subdomains.js.map +1 -0
- package/dist/src/server-bootstrap/routes-loader.d.ts.map +1 -1
- package/dist/src/server-bootstrap/routes-loader.js +12 -1
- package/dist/src/server-bootstrap/routes-loader.js.map +1 -1
- package/dist/src/server.d.ts.map +1 -1
- package/dist/src/server.js +4 -0
- package/dist/src/server.js.map +1 -1
- package/dist/src/services/apps-backup-export.d.ts +22 -0
- package/dist/src/services/apps-backup-export.d.ts.map +1 -0
- package/dist/src/services/apps-backup-export.js +136 -0
- package/dist/src/services/apps-backup-export.js.map +1 -0
- package/dist/src/services/apps-backup-import.d.ts +120 -0
- package/dist/src/services/apps-backup-import.d.ts.map +1 -0
- package/dist/src/services/apps-backup-import.js +259 -0
- package/dist/src/services/apps-backup-import.js.map +1 -0
- package/dist/src/services/component-registrar.d.ts +17 -0
- package/dist/src/services/component-registrar.d.ts.map +1 -1
- package/dist/src/services/component-registrar.js +63 -5
- package/dist/src/services/component-registrar.js.map +1 -1
- package/dist/src/services/handbooks/appdev.d.ts +1 -1
- package/dist/src/services/handbooks/appdev.d.ts.map +1 -1
- package/dist/src/services/handbooks/appdev.js +5 -2
- package/dist/src/services/handbooks/appdev.js.map +1 -1
- package/dist/src/storage/interface.d.ts +11 -1
- 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 +10 -1
- package/dist/src/storage/providers/mongodb/index.d.ts.map +1 -1
- package/dist/src/storage/providers/mongodb/index.js +76 -0
- package/dist/src/storage/providers/mongodb/index.js.map +1 -1
- package/dist/src/storage/providers/sqlite/index.d.ts +8 -1
- package/dist/src/storage/providers/sqlite/index.d.ts.map +1 -1
- package/dist/src/storage/providers/sqlite/index.js +61 -0
- package/dist/src/storage/providers/sqlite/index.js.map +1 -1
- package/dist/src/storage/providers/sqlite/schema.d.ts.map +1 -1
- package/dist/src/storage/providers/sqlite/schema.js +11 -0
- package/dist/src/storage/providers/sqlite/schema.js.map +1 -1
- package/dist/src/storage/repositories/index.d.ts +1 -0
- package/dist/src/storage/repositories/index.d.ts.map +1 -1
- package/dist/src/storage/repositories/package.repository.d.ts +2 -0
- package/dist/src/storage/repositories/package.repository.d.ts.map +1 -1
- package/dist/src/storage/repositories/subdomain-site.repository.d.ts +19 -0
- package/dist/src/storage/repositories/subdomain-site.repository.d.ts.map +1 -0
- package/dist/src/storage/repositories/subdomain-site.repository.js +2 -0
- package/dist/src/storage/repositories/subdomain-site.repository.js.map +1 -0
- package/dist/static/app-catalog.html +1139 -28
- package/package.json +1 -1
- package/prisma/schema.postgres.prisma +11 -0
- package/prisma/schema.prisma +11 -0
|
@@ -23,8 +23,11 @@ import { StatusDot } from '/components/StatusDot.js';
|
|
|
23
23
|
import { useConfirm } from '/components/Modal.js';
|
|
24
24
|
import * as nodesService from '/js/services/nodes.js';
|
|
25
25
|
import { getNodeUrl } from '/js/services/auth.js';
|
|
26
|
+
import NodeStatsTab from './node-stats-tab.js';
|
|
26
27
|
|
|
27
|
-
export
|
|
28
|
+
/* Default export below wraps the node list and Node stats into one page with
|
|
29
|
+
* sub-tabs — Node stats left the sidebar menu (2026-06-10 sidebar reorg). */
|
|
30
|
+
function NodesList({ session, showToast, onStats }) {
|
|
28
31
|
const { confirm, ConfirmUI } = useConfirm();
|
|
29
32
|
const NODE_URL = getNodeUrl();
|
|
30
33
|
const tunnelUrl = NODE_URL.replace(/^http/, 'ws') + '/v1/personal/tunnel';
|
|
@@ -102,7 +105,7 @@ export default function NodesTab({ session, showToast, onStats }) {
|
|
|
102
105
|
<div class="pn-card">
|
|
103
106
|
<div class="pn-header" onClick=${() => {
|
|
104
107
|
const s = new Set(expandedNodes);
|
|
105
|
-
s.has(idx)
|
|
108
|
+
if (s.has(idx)) s.delete(idx); else s.add(idx);
|
|
106
109
|
setExpandedNodes(s);
|
|
107
110
|
}}>
|
|
108
111
|
<div class="pn-header-left">
|
|
@@ -151,7 +154,7 @@ export default function NodesTab({ session, showToast, onStats }) {
|
|
|
151
154
|
<div class="flex-actions">
|
|
152
155
|
<button class="expand-btn btn-sm" onClick=${() => {
|
|
153
156
|
const s = new Set(expandedSetups);
|
|
154
|
-
s.has(idx)
|
|
157
|
+
if (s.has(idx)) s.delete(idx); else s.add(idx);
|
|
155
158
|
setExpandedSetups(s);
|
|
156
159
|
}}>${t('profile.nodes.setupTitle')} <span class="pn-setup-arrow ${setupOpen ? 'open' : ''}">\u25BC</span></button>
|
|
157
160
|
${setupOpen && html`
|
|
@@ -200,3 +203,15 @@ function NodeForm({ onRegister, onCancel }) {
|
|
|
200
203
|
</div>
|
|
201
204
|
</div>`;
|
|
202
205
|
}
|
|
206
|
+
|
|
207
|
+
/* ── Page wrapper: Nodes | Node stats sub-tabs (Node stats merged here from the menu) ── */
|
|
208
|
+
export default function NodesTab(props) {
|
|
209
|
+
const [sub, setSub] = useState('nodes');
|
|
210
|
+
return html`
|
|
211
|
+
<div class="sub-tabs">
|
|
212
|
+
<button class="sub-tab ${sub === 'nodes' ? 'active' : ''}" onClick=${() => setSub('nodes')}>${t('profile.tabs.nodes')}</button>
|
|
213
|
+
<button class="sub-tab ${sub === 'stats' ? 'active' : ''}" onClick=${() => setSub('stats')}>${t('profile.tabs.nodeStats')}</button>
|
|
214
|
+
</div>
|
|
215
|
+
${sub === 'nodes' ? html`<${NodesList} ...${props} />` : html`<${NodeStatsTab} ...${props} />`}
|
|
216
|
+
`;
|
|
217
|
+
}
|
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file offers-tab.js
|
|
3
|
+
* @description The Agent Offers "Do" surface — goal-first "what do you want to do with your agents".
|
|
4
|
+
* Aggregates every agent's published offers into one searchable list with trust badges
|
|
5
|
+
* (cost/latency/verification/dataHandling/availability), and a mode-aware Ask (Run / Copy prompt /
|
|
6
|
+
* Run now) that returns a result with a provenance footer. See docs/plans/2026-06-12-agent-offers-*.md.
|
|
7
|
+
* @structure default OffersTab({ session, showToast }) · OfferCard · badges
|
|
8
|
+
* @version-history
|
|
9
|
+
* v1.0.0 -- 2026-06-12 -- Initial v1: Do feed + offer detail + mode-aware Ask + provenance footer.
|
|
10
|
+
* v1.1.0 -- 2026-06-12 -- Billable offers: price/visibility badges + an owner Selling editor (price +
|
|
11
|
+
* visibility) on the offer card, persisted via setOfferBilling.
|
|
12
|
+
* v1.2.0 -- 2026-06-12 -- Inbox shows the real deliverable (read from the agent namespace, markdown-
|
|
13
|
+
* rendered) even when deliverableKey is unset; sample rendered as markdown; request box clears after
|
|
14
|
+
* Ask; "Requested" links to the Inbox.
|
|
15
|
+
*/
|
|
16
|
+
import { h } from 'preact';
|
|
17
|
+
import { useState, useEffect, useCallback, useRef } from 'preact/hooks';
|
|
18
|
+
import htm from 'htm';
|
|
19
|
+
const html = htm.bind(h);
|
|
20
|
+
import { t } from '/js/i18n.js';
|
|
21
|
+
import { escHtml } from '/js/utils.js';
|
|
22
|
+
import { useConfirm } from '/components/Modal.js';
|
|
23
|
+
import { Markdown } from '/components/Markdown.js';
|
|
24
|
+
import { dt } from '/js/format.js';
|
|
25
|
+
import * as offersService from '/js/services/offers.js';
|
|
26
|
+
|
|
27
|
+
const askLabel = (entry, offer) => {
|
|
28
|
+
if (offer?.availability?.scheduleBorn) return t('profile.offers.runNow') || '⏱ Run now';
|
|
29
|
+
const m = entry.mode || 'interactive';
|
|
30
|
+
return (m === 'task-runner' || m === 'autonomous') ? (t('profile.offers.run') || '▶ Run') : (t('profile.offers.copyPrompt') || '⧉ Copy prompt');
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
function Badges({ offer }) {
|
|
34
|
+
const chips = [];
|
|
35
|
+
if (offer.visibility && offer.visibility !== 'private') chips.push(['vis', t('profile.offers.visibility.' + offer.visibility) || offer.visibility]);
|
|
36
|
+
if (offer.price && offer.price.morsels > 0) chips.push(['price', offer.price.morsels + ' ' + (t('profile.offers.morsels') || 'morsels')]);
|
|
37
|
+
if (offer.verification) chips.push(['ver', t('profile.offers.verification.' + offer.verification) || offer.verification]);
|
|
38
|
+
if (offer.dataHandling) chips.push(['data', t('profile.offers.dataHandling.' + offer.dataHandling) || offer.dataHandling]);
|
|
39
|
+
if (offer.cost) chips.push(['cost', t('profile.offers.cost.' + offer.cost) || offer.cost]);
|
|
40
|
+
if (offer.latency) chips.push(['lat', t('profile.offers.latency.' + offer.latency) || offer.latency]);
|
|
41
|
+
return html`<span class="of-badges">${chips.map(([k, label]) => html`<span class="of-badge of-badge--${k}" key=${k}>${escHtml(label)}</span>`)}</span>`;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function OfferCard({ entry, offer, showToast, confirm, busy, setBusy, onChanged, onGoInbox }) {
|
|
45
|
+
const [open, setOpen] = useState(false);
|
|
46
|
+
const [input, setInput] = useState('');
|
|
47
|
+
const [result, setResult] = useState(null);
|
|
48
|
+
const [vis, setVis] = useState(offer.visibility || 'private');
|
|
49
|
+
const [morsels, setMorsels] = useState(offer.price?.morsels ?? 0);
|
|
50
|
+
const [savingBill, setSavingBill] = useState(false);
|
|
51
|
+
|
|
52
|
+
const consequences = offer.consequences || [];
|
|
53
|
+
const gated = consequences.some(c => c.persistent || c.requiresApproval || c.type === 'external-send' || c.type === 'mutates-host' || c.type === 'publishes-public');
|
|
54
|
+
|
|
55
|
+
const doAsk = async () => {
|
|
56
|
+
setBusy(true);
|
|
57
|
+
try {
|
|
58
|
+
const r = await offersService.ask(entry, offer, input);
|
|
59
|
+
if (r.ok === false) { showToast(r.error?.message || (t('profile.offers.askFailed') || 'Ask failed')); setResult(null); }
|
|
60
|
+
else {
|
|
61
|
+
setResult({ ...r, at: new Date().toISOString() });
|
|
62
|
+
setInput(''); // clear the request box — it has been submitted
|
|
63
|
+
if (r.kind === 'prompt') showToast(t('profile.offers.promptCopied') || 'Prompt copied — paste it into the agent’s chat.');
|
|
64
|
+
else if (r.kind === 'triggered') showToast(t('profile.offers.triggered') || 'Schedule triggered.');
|
|
65
|
+
else if (r.kind === 'task') showToast((t('profile.offers.requested') || 'Requested — task queued for {agent}.').replace('{agent}', entry.agent));
|
|
66
|
+
}
|
|
67
|
+
} catch (e) { showToast((e && e.message) || (t('profile.offers.askFailed') || 'Ask failed')); }
|
|
68
|
+
finally { setBusy(false); }
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const onAsk = () => {
|
|
72
|
+
if (gated) {
|
|
73
|
+
confirm(
|
|
74
|
+
(t('profile.offers.confirmAsk') || 'This has lasting effects ({effects}). Ask anyway?').replace('{effects}', consequences.map(c => c.type).join(', ')),
|
|
75
|
+
doAsk, { danger: true });
|
|
76
|
+
} else doAsk();
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
const saveBilling = async () => {
|
|
80
|
+
setSavingBill(true);
|
|
81
|
+
try {
|
|
82
|
+
const price = Number(morsels) > 0 ? { morsels: Number(morsels), unit: 'per-call' } : null;
|
|
83
|
+
const r = await offersService.setOfferBilling(entry.agent, offer.id, { price, visibility: vis });
|
|
84
|
+
if (r?.ok === false) showToast(r?.error?.message || (t('profile.offers.billSaveFailed') || 'Could not save'));
|
|
85
|
+
else { showToast(t('profile.offers.billSaved') || 'Saved.'); onChanged?.(); }
|
|
86
|
+
} catch (e) { showToast((e && e.message) || (t('profile.offers.billSaveFailed') || 'Could not save')); }
|
|
87
|
+
finally { setSavingBill(false); }
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
return html`
|
|
91
|
+
<div class="of-card ${open ? 'of-card--open' : ''}">
|
|
92
|
+
<div class="of-card-head" onClick=${() => setOpen(o => !o)}>
|
|
93
|
+
<div class="of-card-title">
|
|
94
|
+
<span class="of-offer-title">${escHtml(offer.title)}</span>
|
|
95
|
+
<span class="of-agent">${escHtml(entry.agent)} <span class="of-dot ${entry.online ? 'of-dot--on' : 'of-dot--off'}" title=${entry.online ? (t('profile.offers.online') || 'online') : (t('profile.offers.offline') || 'last seen ' + (entry.last_seen ? dt(entry.last_seen) : '—'))}></span></span>
|
|
96
|
+
</div>
|
|
97
|
+
<${Badges} offer=${offer} />
|
|
98
|
+
</div>
|
|
99
|
+
<div class="of-ask">${escHtml(offer.ask)}</div>
|
|
100
|
+
|
|
101
|
+
${open ? html`
|
|
102
|
+
<div class="of-detail">
|
|
103
|
+
${offer.example ? html`<div class="of-example"><span class="of-label">${t('profile.offers.example') || 'Example'}:</span> ${escHtml(offer.example)}</div>` : null}
|
|
104
|
+
|
|
105
|
+
${(offer.requirements || []).length ? html`
|
|
106
|
+
<div class="of-label">${t('profile.offers.requirements') || 'Before you ask'}</div>
|
|
107
|
+
<div class="of-req-list">${offer.requirements.map((r, i) => html`
|
|
108
|
+
<span class="of-req" key=${i}>${escHtml(r.need)}${r.instruction ? html` <span class="of-mini">— ${escHtml(r.instruction)}</span>` : null}${r.fix ? html` <span class="of-fix">${escHtml(r.fix)}</span>` : null}</span>`)}</div>
|
|
109
|
+
` : null}
|
|
110
|
+
|
|
111
|
+
${consequences.length ? html`
|
|
112
|
+
<div class="of-label">${t('profile.offers.consequences') || 'What this does'}</div>
|
|
113
|
+
<div class="of-conseq-list">${consequences.map((c, i) => html`
|
|
114
|
+
<span class="of-conseq ${(c.persistent || c.requiresApproval || c.type === 'mutates-host') ? 'of-conseq--warn' : ''}" key=${i}>
|
|
115
|
+
${escHtml(t('profile.offers.consequence.' + c.type) || c.type)}${c.requiresApproval ? ' ⚠' : ''}${c.dynamic ? ' ↯' : ''}</span>`)}</div>
|
|
116
|
+
` : null}
|
|
117
|
+
|
|
118
|
+
${offer.deliverable ? html`
|
|
119
|
+
<div class="of-label">${t('profile.offers.deliverable') || 'You’ll get back'}: ${escHtml(offer.deliverable.format || '')}${offer.deliverable.location?.space ? html` <span class="of-mini">→ ${escHtml(offer.deliverable.location.space)}</span>` : null}</div>
|
|
120
|
+
${offer.deliverable.sample === 'untested'
|
|
121
|
+
? html`<span class="of-badge of-badge--untested">${t('profile.offers.untested') || 'untested — no sample yet'}</span>`
|
|
122
|
+
: (typeof offer.deliverable.sample === 'string'
|
|
123
|
+
? html`<div class="of-md of-sample-md"><${Markdown} text=${offer.deliverable.sample} /></div>`
|
|
124
|
+
: html`<pre class="of-sample">${escHtml(JSON.stringify(offer.deliverable.sample, null, 2))}</pre>`)}
|
|
125
|
+
` : null}
|
|
126
|
+
|
|
127
|
+
<textarea class="input-field input-sm of-input" rows="2" placeholder=${t('profile.offers.requestPlaceholder') || 'Your request (optional — fills the example)…'} value=${input} onInput=${(e) => setInput(e.target.value)}></textarea>
|
|
128
|
+
<div class="flex-row-wrap">
|
|
129
|
+
<button class="btn-primary btn-sm" disabled=${busy} onClick=${onAsk}>${askLabel(entry, offer)}</button>
|
|
130
|
+
</div>
|
|
131
|
+
|
|
132
|
+
<div class="of-bill">
|
|
133
|
+
<div class="of-label">${t('profile.offers.selling') || 'Selling'}</div>
|
|
134
|
+
<div class="flex-row-wrap of-bill-row">
|
|
135
|
+
<select class="input-field input-sm of-bill-vis" value=${vis} onChange=${(e) => setVis(e.target.value)}>
|
|
136
|
+
<option value="private">${t('profile.offers.visibility.private') || 'Private (only me)'}</option>
|
|
137
|
+
<option value="unlisted">${t('profile.offers.visibility.unlisted') || 'Unlisted (by link)'}</option>
|
|
138
|
+
<option value="public">${t('profile.offers.visibility.public') || 'Public (listed)'}</option>
|
|
139
|
+
</select>
|
|
140
|
+
<input type="number" min="0" class="input-field input-sm of-bill-price" value=${morsels} onInput=${(e) => setMorsels(e.target.value)} placeholder=${t('profile.offers.priceMorsels') || 'Price'} />
|
|
141
|
+
<span class="of-mini">${t('profile.offers.morsels') || 'morsels'} / ${t('profile.offers.perCall') || 'call'}</span>
|
|
142
|
+
<button class="btn-outline btn-sm" disabled=${savingBill} onClick=${saveBilling}>${t('profile.offers.saveBilling') || 'Save'}</button>
|
|
143
|
+
</div>
|
|
144
|
+
${vis !== 'private' && Number(morsels) > 0 ? html`<div class="of-mini">${(t('profile.offers.billHint') || 'Others pay {n} morsels per call; you keep it minus the marketplace fee. Your own use is free.').replace('{n}', morsels)}</div>` : null}
|
|
145
|
+
${vis !== 'private' && !offer.callable ? html`<div class="of-mini of-warn">${t('profile.offers.notCallableHint') || 'Not machine-callable yet — others can find it, but invoking it needs a capability binding (action_id).'}</div>` : null}
|
|
146
|
+
</div>
|
|
147
|
+
|
|
148
|
+
${result ? html`
|
|
149
|
+
<div class="of-result">
|
|
150
|
+
${result.kind === 'prompt' ? html`<div class="of-result-msg">${t('profile.offers.promptCopied') || 'Prompt copied — paste it into the agent’s chat.'}</div>` : null}
|
|
151
|
+
${result.kind === 'triggered' ? html`<div class="of-result-msg">${t('profile.offers.triggered') || 'Schedule triggered.'}</div>` : null}
|
|
152
|
+
${result.kind === 'task' ? html`<div class="of-result-msg">${(t('profile.offers.requested') || 'Requested — task queued for {agent}.').replace('{agent}', entry.agent)}${onGoInbox ? html` <button class="of-link" onClick=${onGoInbox}>${t('profile.offers.openInbox') || 'See it in the Inbox →'}</button>` : null}</div>` : null}
|
|
153
|
+
<div class="of-prov">${t('profile.offers.provenance') || 'From'}: <b>${escHtml(entry.agent)}</b>${result.taskId ? html` · ${t('profile.offers.task') || 'task'} ${escHtml(String(result.taskId))} · ${t('profile.offers.queued') || 'queued'}` : ''} · ${dt(result.at)}</div>
|
|
154
|
+
</div>
|
|
155
|
+
` : null}
|
|
156
|
+
</div>
|
|
157
|
+
` : null}
|
|
158
|
+
</div>
|
|
159
|
+
`;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const STATUS_CLASS = { done: 'of-st--done', failed: 'of-st--failed', active: 'of-st--active', paused: 'of-st--active', stalled: 'of-st--failed', queued: 'of-st--queued', revision_requested: 'of-st--queued' };
|
|
163
|
+
|
|
164
|
+
function Stars({ value, onPick, disabled }) {
|
|
165
|
+
return html`<span class="of-stars">${[1, 2, 3, 4, 5].map(n => html`
|
|
166
|
+
<button class="of-star ${n <= value ? 'of-star--on' : ''}" key=${n} disabled=${disabled} onClick=${() => onPick(n)} title=${String(n)}>${n <= value ? '★' : '☆'}</button>`)}</span>`;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function DeliverableRow({ d, showToast, onChanged }) {
|
|
170
|
+
const [open, setOpen] = useState(false);
|
|
171
|
+
const [content, setContent] = useState(undefined); // undefined = not loaded, null = gone
|
|
172
|
+
const [stars, setStars] = useState(d.rating?.stars || 0);
|
|
173
|
+
const [comment, setComment] = useState('');
|
|
174
|
+
const [busy, setBusy] = useState(false);
|
|
175
|
+
|
|
176
|
+
const toggle = async () => {
|
|
177
|
+
const next = !open; setOpen(next);
|
|
178
|
+
// Fetch on first open for any non-failed task — the deliverable lives in the
|
|
179
|
+
// agent's namespace and is found by deliverableKey OR the task:<id> tag, so we
|
|
180
|
+
// don't gate on deliverable_key (CrewAI agents often never set it).
|
|
181
|
+
if (next && content === undefined && d.status !== 'failed' && d.agent_gaii) {
|
|
182
|
+
setContent('loading');
|
|
183
|
+
const v = await offersService.getDeliverableContent({ agentGaii: d.agent_gaii, deliverableKey: d.deliverable_key, taskId: d.task_id }).catch(() => null);
|
|
184
|
+
setContent(v ?? null);
|
|
185
|
+
}
|
|
186
|
+
};
|
|
187
|
+
const submitRating = async () => {
|
|
188
|
+
if (!stars) return;
|
|
189
|
+
setBusy(true);
|
|
190
|
+
try {
|
|
191
|
+
const r = await offersService.rateDeliverable(d.agent, d.task_id, { stars, comment: comment.trim() || undefined });
|
|
192
|
+
if (r?.ok === false) showToast(r?.error?.message || (t('profile.offers.rateFailed') || 'Rating failed'));
|
|
193
|
+
else { showToast(t('profile.offers.rated') || 'Thanks — rated.'); setComment(''); onChanged?.(); }
|
|
194
|
+
} catch (e) { showToast((e && e.message) || (t('profile.offers.rateFailed') || 'Rating failed')); }
|
|
195
|
+
finally { setBusy(false); }
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
return html`
|
|
199
|
+
<div class="of-card ${open ? 'of-card--open' : ''}">
|
|
200
|
+
<div class="of-card-head" onClick=${toggle}>
|
|
201
|
+
<div class="of-card-title">
|
|
202
|
+
<span class="of-offer-title">${escHtml(d.title || d.task_id)}</span>
|
|
203
|
+
<span class="of-agent">${escHtml(d.agent)}</span>
|
|
204
|
+
</div>
|
|
205
|
+
<span class="of-status ${STATUS_CLASS[d.status] || ''}">${t('profile.offers.status.' + d.status) || d.status}</span>
|
|
206
|
+
</div>
|
|
207
|
+
${open ? html`
|
|
208
|
+
<div class="of-detail">
|
|
209
|
+
${d.verification ? html`<div class="of-mini">${t('profile.offers.expected') || 'Expected'}: ${escHtml(d.verification)}</div>` : null}
|
|
210
|
+
${d.status === 'failed' ? html`<div class="of-fail">${t('profile.offers.failedMsg') || 'This task failed — no deliverable was produced.'}</div>`
|
|
211
|
+
: content === undefined ? null
|
|
212
|
+
: content === 'loading' ? html`<div class="of-mini">…</div>`
|
|
213
|
+
: content === null ? html`<div class="of-mini">${t('profile.offers.noDeliverableYet') || 'No deliverable yet.'}</div>`
|
|
214
|
+
: html`<div class="of-md"><${Markdown} text=${content} /></div>`}
|
|
215
|
+
|
|
216
|
+
<div class="of-prov">${t('profile.offers.provenance') || 'From'}: <b>${escHtml(d.agent)}</b> · ${t('profile.offers.task') || 'task'} ${escHtml(String(d.task_id))} · ${t('profile.offers.status.' + d.status) || d.status} · ${dt(d.updated_at)}</div>
|
|
217
|
+
|
|
218
|
+
${d.status === 'done' ? html`
|
|
219
|
+
<div class="of-rate">
|
|
220
|
+
<span class="of-label">${d.rating ? (t('profile.offers.yourRating') || 'Your rating') : (t('profile.offers.rateIt') || 'Rate it')}</span>
|
|
221
|
+
<${Stars} value=${stars} disabled=${busy} onPick=${(n) => setStars(n)} />
|
|
222
|
+
<input class="input-field input-sm of-rate-note" placeholder=${t('profile.offers.ratePlaceholder') || 'Optional note…'} value=${comment} onInput=${(e) => setComment(e.target.value)} />
|
|
223
|
+
<button class="btn-primary btn-sm" disabled=${busy || !stars} onClick=${submitRating}>${t('profile.offers.submitRating') || 'Submit'}</button>
|
|
224
|
+
</div>
|
|
225
|
+
` : null}
|
|
226
|
+
</div>
|
|
227
|
+
` : null}
|
|
228
|
+
</div>
|
|
229
|
+
`;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function InboxFeed({ showToast }) {
|
|
233
|
+
const [items, setItems] = useState(null);
|
|
234
|
+
const load = useCallback(async () => {
|
|
235
|
+
const r = await offersService.listDeliverables().catch(() => null);
|
|
236
|
+
setItems(r?.data?.deliverables || []);
|
|
237
|
+
}, []);
|
|
238
|
+
useEffect(() => { load(); }, [load]);
|
|
239
|
+
const liveRef = useRef(load); liveRef.current = load;
|
|
240
|
+
useEffect(() => {
|
|
241
|
+
const h = () => liveRef.current();
|
|
242
|
+
window.addEventListener('aimeat-live-update', h);
|
|
243
|
+
return () => window.removeEventListener('aimeat-live-update', h);
|
|
244
|
+
}, []);
|
|
245
|
+
return html`
|
|
246
|
+
<div>
|
|
247
|
+
${items === null ? html`<div class="section-desc">…</div>` : null}
|
|
248
|
+
${items !== null && items.length === 0 ? html`<div class="empty">${t('profile.offers.inboxEmpty') || 'Nothing has come back yet. Ask an agent something in “What can I do?”.'}</div>` : null}
|
|
249
|
+
${(items || []).map(d => html`<${DeliverableRow} key=${d.task_id} d=${d} showToast=${showToast} onChanged=${load} />`)}
|
|
250
|
+
</div>
|
|
251
|
+
`;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
export default function OffersTab({ session, showToast }) {
|
|
255
|
+
const { confirm, ConfirmUI } = useConfirm();
|
|
256
|
+
const [view, setView] = useState('do'); // 'do' (activate) | 'inbox' (follow-up)
|
|
257
|
+
const [feed, setFeed] = useState(null); // null = loading
|
|
258
|
+
const [q, setQ] = useState('');
|
|
259
|
+
const [busy, setBusy] = useState(false);
|
|
260
|
+
|
|
261
|
+
const load = useCallback(async () => {
|
|
262
|
+
try {
|
|
263
|
+
const r = await offersService.listOffers();
|
|
264
|
+
setFeed(r?.data?.agents || []);
|
|
265
|
+
} catch { setFeed([]); }
|
|
266
|
+
}, []);
|
|
267
|
+
useEffect(() => { if (session) load(); }, [session, load]);
|
|
268
|
+
const liveRef = useRef(load); liveRef.current = load;
|
|
269
|
+
useEffect(() => {
|
|
270
|
+
const handler = () => liveRef.current();
|
|
271
|
+
window.addEventListener('aimeat-live-update', handler);
|
|
272
|
+
return () => window.removeEventListener('aimeat-live-update', handler);
|
|
273
|
+
}, []);
|
|
274
|
+
|
|
275
|
+
// Flatten to a searchable list of { entry, offer }.
|
|
276
|
+
const items = [];
|
|
277
|
+
for (const entry of (feed || [])) for (const offer of (entry.offers || [])) items.push({ entry, offer });
|
|
278
|
+
const needle = q.trim().toLowerCase();
|
|
279
|
+
const filtered = needle
|
|
280
|
+
? items.filter(({ entry, offer }) =>
|
|
281
|
+
(offer.title + ' ' + offer.ask + ' ' + (offer.tags || []).join(' ') + ' ' + entry.agent).toLowerCase().includes(needle))
|
|
282
|
+
: items;
|
|
283
|
+
|
|
284
|
+
return html`
|
|
285
|
+
<div class="of">
|
|
286
|
+
<div class="section-title">${view === 'do' ? (t('profile.offers.title') || 'What can I do?') : (t('profile.offers.inboxTitle') || 'What came back')}</div>
|
|
287
|
+
<div class="section-desc">${view === 'do' ? (t('profile.offers.desc') || 'Everything your agents can do for you — search, then ask. No clicking through agents and tabs.') : (t('profile.offers.inboxDesc') || 'Everything your agents delivered — check it and rate it, all in one place.')}</div>
|
|
288
|
+
|
|
289
|
+
<div class="of-seg">
|
|
290
|
+
<button class="of-seg-btn ${view === 'do' ? 'of-seg-btn--on' : ''}" onClick=${() => setView('do')}>${t('profile.offers.segDo') || 'Do'}</button>
|
|
291
|
+
<button class="of-seg-btn ${view === 'inbox' ? 'of-seg-btn--on' : ''}" onClick=${() => setView('inbox')}>${t('profile.offers.segInbox') || 'Inbox'}</button>
|
|
292
|
+
</div>
|
|
293
|
+
|
|
294
|
+
${view === 'inbox' ? html`<${InboxFeed} showToast=${showToast} />` : html`
|
|
295
|
+
<input class="input-field input-sm of-search" placeholder=${t('profile.offers.searchPlaceholder') || 'What do you want to do?…'} value=${q} onInput=${(e) => setQ(e.target.value)} />
|
|
296
|
+
|
|
297
|
+
${feed === null ? html`<div class="section-desc">…</div>` : null}
|
|
298
|
+
${feed !== null && items.length === 0 ? html`<div class="empty">${t('profile.offers.empty') || 'None of your agents publish offers yet.'}</div>` : null}
|
|
299
|
+
${feed !== null && items.length > 0 && filtered.length === 0 ? html`<div class="section-desc">${t('profile.offers.noMatch') || 'No offers match.'}</div>` : null}
|
|
300
|
+
|
|
301
|
+
${filtered.map(({ entry, offer }) => html`<${OfferCard}
|
|
302
|
+
key=${entry.agent + '/' + offer.id}
|
|
303
|
+
entry=${entry} offer=${offer}
|
|
304
|
+
showToast=${showToast} confirm=${confirm} busy=${busy} setBusy=${setBusy} onChanged=${load} onGoInbox=${() => setView('inbox')} />`)}
|
|
305
|
+
`}
|
|
306
|
+
|
|
307
|
+
<${ConfirmUI} />
|
|
308
|
+
</div>
|
|
309
|
+
`;
|
|
310
|
+
}
|