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
|
@@ -29,6 +29,122 @@ import { useConfirm } from '/components/Modal.js';
|
|
|
29
29
|
import * as pkgService from '/js/services/packages.js';
|
|
30
30
|
import { importPackageToGenerator } from '/js/services/generator-packaging.js';
|
|
31
31
|
|
|
32
|
+
// ─── InstanceCard ───────────────────────────────────────────────────────────
|
|
33
|
+
// Expanded card for one installed package instance: shows every component's
|
|
34
|
+
// status + type, with direct action buttons (Launch an app in a new tab,
|
|
35
|
+
// jump to extensions/cortex tab for management, copy the registered name).
|
|
36
|
+
// Components produced via packages-install are stored with status:'active'
|
|
37
|
+
// at creation time (see component-registrar.ts), so the green "active" chip
|
|
38
|
+
// here matches reality without re-fetching the underlying extension/cortex.
|
|
39
|
+
// Profile reads ?tab=<id> on mount and routes to the matching tab; hash
|
|
40
|
+
// fragments don't switch tabs. The IDs here are the profile tab IDs.
|
|
41
|
+
const COMP_TYPE_META = {
|
|
42
|
+
app: { icon: '\u{1F4F1}', tabId: 'apps' },
|
|
43
|
+
extension: { icon: '\u{1F50C}', tabId: 'extensions' },
|
|
44
|
+
cortex: { icon: '\u{1F9E0}', tabId: 'extensions' },
|
|
45
|
+
memory: { icon: '\u{1F4BE}', tabId: 'memory' },
|
|
46
|
+
translation: { icon: '\u{1F310}', tabId: 'memory' },
|
|
47
|
+
csm: { icon: '\u{1F4D0}', tabId: 'memory' },
|
|
48
|
+
msm: { icon: '\u{1F4DC}', tabId: 'memory' },
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
function InstanceCard({ inst, session, onCheckUpdate, onRemove, navigate }) {
|
|
52
|
+
const [expanded, setExpanded] = useState(true);
|
|
53
|
+
const comps = inst.installedComponents || [];
|
|
54
|
+
const owner = session?.owner || '';
|
|
55
|
+
|
|
56
|
+
// Build a deterministic action per component type.
|
|
57
|
+
function actionsFor(c) {
|
|
58
|
+
const meta = COMP_TYPE_META[c.type] || {};
|
|
59
|
+
const acts = [];
|
|
60
|
+
if (c.type === 'app') {
|
|
61
|
+
const url = `/v1/apps/${encodeURIComponent(owner)}/${encodeURIComponent(c.registeredAs)}?mode=inline`;
|
|
62
|
+
acts.push(html`
|
|
63
|
+
<a class="btn-primary btn-sm pf-no-underline" href=${url} target="_blank" rel="noopener">
|
|
64
|
+
${t('packages.launch') || 'Launch'}
|
|
65
|
+
</a>
|
|
66
|
+
`);
|
|
67
|
+
}
|
|
68
|
+
if (c.type === 'extension' || c.type === 'cortex') {
|
|
69
|
+
acts.push(html`
|
|
70
|
+
<button class="btn-outline btn-sm" onClick=${() => {
|
|
71
|
+
// Profile reads ?tab=<id> on mount; this preserves the activeTab
|
|
72
|
+
// state via the same path the landing-page tab cards use.
|
|
73
|
+
const url = `/v1/profile?tab=${meta.tabId || 'extensions'}`;
|
|
74
|
+
if (typeof navigate === 'function') navigate(url);
|
|
75
|
+
else window.location.href = url;
|
|
76
|
+
}}>
|
|
77
|
+
${t('packages.manage') || 'Manage'}
|
|
78
|
+
</button>
|
|
79
|
+
`);
|
|
80
|
+
}
|
|
81
|
+
if (c.type === 'memory' || c.type === 'translation' || c.type === 'csm' || c.type === 'msm') {
|
|
82
|
+
acts.push(html`
|
|
83
|
+
<button class="btn-outline btn-sm" onClick=${() => {
|
|
84
|
+
const url = `/v1/profile?tab=${meta.tabId || 'memory'}`;
|
|
85
|
+
if (typeof navigate === 'function') navigate(url);
|
|
86
|
+
else window.location.href = url;
|
|
87
|
+
}}>
|
|
88
|
+
${t('packages.inspect') || 'Inspect'}
|
|
89
|
+
</button>
|
|
90
|
+
`);
|
|
91
|
+
}
|
|
92
|
+
// Copy registered name — useful for debugging or wiring up custom things.
|
|
93
|
+
acts.push(html`
|
|
94
|
+
<button class="btn-ghost btn-sm" title=${c.registeredAs} onClick=${() => copyToClipboard(c.registeredAs)}>
|
|
95
|
+
\u{1F4CB}
|
|
96
|
+
</button>
|
|
97
|
+
`);
|
|
98
|
+
return acts;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// INSTALLED == installed AND active+running. Show a clarifying sub-label so
|
|
102
|
+
// the user doesn't confuse it with the two-step extension/cortex lifecycle.
|
|
103
|
+
const installedSubLabel = inst.status === 'installed'
|
|
104
|
+
? (t('packages.statusInstalledActive') || 'active & running')
|
|
105
|
+
: '';
|
|
106
|
+
|
|
107
|
+
return html`
|
|
108
|
+
<div class="pkg-card pkg-card-expanded" key=${inst.id}>
|
|
109
|
+
<div class="pkg-card-header">
|
|
110
|
+
<span class="pkg-card-name">${escHtml(inst.label || inst.packageGroupId)}</span>
|
|
111
|
+
<span class="pkg-badge pkg-badge-${inst.status}" title=${installedSubLabel}>
|
|
112
|
+
${inst.status}${installedSubLabel ? html` <span class="pkg-badge-sub">· ${installedSubLabel}</span>` : ''}
|
|
113
|
+
</span>
|
|
114
|
+
</div>
|
|
115
|
+
<div class="pkg-card-meta">
|
|
116
|
+
<span class="pkg-card-version">${escHtml(inst.packageVersion)}</span>
|
|
117
|
+
<span>· ${comps.length} ${t('packages.components') || 'components'}</span>
|
|
118
|
+
<button class="btn-ghost btn-xs" onClick=${() => setExpanded(v => !v)}>
|
|
119
|
+
${expanded ? (t('common.hide') || 'Hide') : (t('common.show') || 'Show')}
|
|
120
|
+
</button>
|
|
121
|
+
</div>
|
|
122
|
+
${expanded && comps.length > 0 && html`
|
|
123
|
+
<ul class="pkg-comp-list">
|
|
124
|
+
${comps.map(c => html`
|
|
125
|
+
<li class="pkg-comp" key=${c.componentId}>
|
|
126
|
+
<span class="pkg-comp-icon">${COMP_TYPE_META[c.type]?.icon || '\u{1F4E6}'}</span>
|
|
127
|
+
<span class="pkg-comp-id">${escHtml(c.componentId)}</span>
|
|
128
|
+
<span class="pkg-comp-type">${c.type}</span>
|
|
129
|
+
<span class="pkg-comp-status">
|
|
130
|
+
<span class="pkg-dot pkg-dot-active" title=${t('packages.statusActive') || 'Active'}></span>
|
|
131
|
+
${c.customized ? html`<span class="pkg-comp-customized" title=${t('packages.customizedHint') || 'Customized after install'}>✏</span>` : ''}
|
|
132
|
+
</span>
|
|
133
|
+
<span class="pkg-comp-actions">${actionsFor(c)}</span>
|
|
134
|
+
</li>
|
|
135
|
+
`)}
|
|
136
|
+
</ul>
|
|
137
|
+
`}
|
|
138
|
+
<div class="pkg-card-footer">
|
|
139
|
+
<div class="pkg-card-actions">
|
|
140
|
+
<button class="btn-outline btn-sm" onClick=${onCheckUpdate}>${t('packages.checkUpdate') || 'Check Update'}</button>
|
|
141
|
+
<button class="btn-danger btn-sm" onClick=${onRemove}>${t('packages.remove') || 'Remove'}</button>
|
|
142
|
+
</div>
|
|
143
|
+
</div>
|
|
144
|
+
</div>
|
|
145
|
+
`;
|
|
146
|
+
}
|
|
147
|
+
|
|
32
148
|
export default function PackagesTab({ session, showToast, navigate, locale }) {
|
|
33
149
|
const { confirm, ConfirmUI } = useConfirm();
|
|
34
150
|
const [instances, setInstances] = useState([]);
|
|
@@ -186,6 +302,29 @@ export default function PackagesTab({ session, showToast, navigate, locale }) {
|
|
|
186
302
|
}
|
|
187
303
|
};
|
|
188
304
|
|
|
305
|
+
// Archive every version of a package group (own-author only, enforced server-side).
|
|
306
|
+
// Already-installed instances stay intact — only the package source is removed
|
|
307
|
+
// from the gallery + browse listings so it can no longer be re-installed.
|
|
308
|
+
const handleDeletePackage = (groupId, name) => {
|
|
309
|
+
confirm(
|
|
310
|
+
(t('packages.confirmDeletePackage') || 'Delete the package "{name}" and all its versions? Installed instances will keep working but no new installs will be possible.').replace('{name}', name || groupId),
|
|
311
|
+
async () => {
|
|
312
|
+
try {
|
|
313
|
+
const res = await pkgService.archivePackageGroup(groupId);
|
|
314
|
+
if (res.ok) {
|
|
315
|
+
showToast(t('packages.packageDeleted') || 'Package deleted');
|
|
316
|
+
loadData();
|
|
317
|
+
} else {
|
|
318
|
+
showToast(res.error || (t('packages.deleteFailed') || 'Delete failed'), true);
|
|
319
|
+
}
|
|
320
|
+
} catch (e) {
|
|
321
|
+
showToast(e.message, true);
|
|
322
|
+
}
|
|
323
|
+
},
|
|
324
|
+
{ danger: true },
|
|
325
|
+
);
|
|
326
|
+
};
|
|
327
|
+
|
|
189
328
|
const handleSyncFederation = async () => {
|
|
190
329
|
setSyncing(true);
|
|
191
330
|
try {
|
|
@@ -257,22 +396,14 @@ export default function PackagesTab({ session, showToast, navigate, locale }) {
|
|
|
257
396
|
` : html`
|
|
258
397
|
<div class="pkg-grid">
|
|
259
398
|
${instances.map(inst => html`
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
</div>
|
|
269
|
-
<div class="pkg-card-footer">
|
|
270
|
-
<div class="pkg-card-actions">
|
|
271
|
-
<button class="btn-outline btn-sm" onClick=${() => handleCheckUpdate(inst.id)}>${t('packages.checkUpdate') || 'Check Update'}</button>
|
|
272
|
-
<button class="btn-danger btn-sm" onClick=${() => handleRemove(inst.id)}>${t('packages.remove') || 'Remove'}</button>
|
|
273
|
-
</div>
|
|
274
|
-
</div>
|
|
275
|
-
</div>
|
|
399
|
+
<${InstanceCard}
|
|
400
|
+
key=${inst.id}
|
|
401
|
+
inst=${inst}
|
|
402
|
+
session=${session}
|
|
403
|
+
onCheckUpdate=${() => handleCheckUpdate(inst.id)}
|
|
404
|
+
onRemove=${() => handleRemove(inst.id)}
|
|
405
|
+
navigate=${navigate}
|
|
406
|
+
/>
|
|
276
407
|
`)}
|
|
277
408
|
</div>
|
|
278
409
|
`}
|
|
@@ -341,6 +472,11 @@ export default function PackagesTab({ session, showToast, navigate, locale }) {
|
|
|
341
472
|
${t('packages.proposeAsTemplate') || 'Propose as Template'}
|
|
342
473
|
</button>
|
|
343
474
|
`}
|
|
475
|
+
${pkg.author === session?.owner && html`
|
|
476
|
+
<button class="btn-danger btn-sm" onClick=${() => handleDeletePackage(pkg.packageGroupId, pkg.name)}>
|
|
477
|
+
${t('packages.delete') || 'Delete'}
|
|
478
|
+
</button>
|
|
479
|
+
`}
|
|
344
480
|
</div>
|
|
345
481
|
</div>
|
|
346
482
|
</div>
|
|
@@ -1,31 +1,98 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @file portfolio-tab.js
|
|
3
|
-
* @description Profile tab
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
1
|
+
/**
|
|
2
|
+
* @file portfolio-tab.js
|
|
3
|
+
* @description Profile tab for the portfolio. NOT a landing page: with no published
|
|
4
|
+
* portfolio it forwards straight to the builder (an empty two-button page was a wasted
|
|
5
|
+
* click); with one published it earns its place — public URL with copy, last updated,
|
|
6
|
+
* and Visit / Edit / Unpublish.
|
|
7
|
+
* @version-history
|
|
8
|
+
* v2.0.0 — 2026-06-10 — Replace the two-button landing: auto-forward to the builder when
|
|
9
|
+
* nothing is published; published state shows URL + last updated + Visit/Edit/Unpublish.
|
|
10
|
+
* v1.1.0 — 2026-03-17 — Replace inline styles with CSS classes
|
|
11
|
+
* v1.0.0 — 2026-03-16 — Initial portfolio tab
|
|
12
|
+
*/
|
|
13
|
+
import { h } from 'preact';
|
|
14
|
+
import { useState, useEffect } from 'preact/hooks';
|
|
15
|
+
import htm from 'htm';
|
|
16
|
+
const html = htm.bind(h);
|
|
17
|
+
import { t, getLocale } from '/js/i18n.js';
|
|
18
|
+
|
|
19
|
+
// t() echoes the key when a translation is missing (e.g. a server still serving
|
|
20
|
+
// pre-update locales) — fall back to readable English instead of raw keys.
|
|
21
|
+
const tr = (key, fallback) => { const v = t(key); return v && v !== key ? v : fallback; };
|
|
22
|
+
import { apiGet, apiPut } from '/js/api.js';
|
|
23
|
+
import { Spinner } from './shared.js';
|
|
24
|
+
import { CopyButton } from '/components/CopyButton.js';
|
|
25
|
+
import { useConfirm } from '/components/Modal.js';
|
|
26
|
+
|
|
27
|
+
export default function PortfolioTab({ session, navigate, showToast }) {
|
|
28
|
+
const { confirm, ConfirmUI } = useConfirm();
|
|
29
|
+
// undefined = loading, null = no config, object = config
|
|
30
|
+
const [cfg, setCfg] = useState(undefined);
|
|
31
|
+
|
|
32
|
+
const load = () => apiGet('/v1/portfolio/config')
|
|
33
|
+
.then(r => setCfg(r?.data?.config || null))
|
|
34
|
+
.catch(() => setCfg(null));
|
|
35
|
+
useEffect(() => { load(); }, []);
|
|
36
|
+
|
|
37
|
+
// No published portfolio → the builder IS the page. Forward instead of showing
|
|
38
|
+
// two buttons on 90% whitespace. CRITICAL: clear the profile's remembered-open-tab
|
|
39
|
+
// first — otherwise every later /v1/profile visit restores this tab, which forwards
|
|
40
|
+
// again, and the user can never reach the profile home (bounce loop).
|
|
41
|
+
useEffect(() => {
|
|
42
|
+
if (cfg !== undefined && !cfg?.enabled) {
|
|
43
|
+
try { sessionStorage.removeItem('aimeat-profile-tab'); } catch { /* noop */ }
|
|
44
|
+
navigate('/v1/portfolio');
|
|
45
|
+
}
|
|
46
|
+
}, [cfg]);
|
|
47
|
+
|
|
48
|
+
if (cfg === undefined) return html`<${Spinner} text=${t('loading') || 'Loading…'} />`;
|
|
49
|
+
if (!cfg?.enabled) return null; // forwarding to the builder
|
|
50
|
+
|
|
51
|
+
const url = `${window.location.origin}/v1/portfolio/${encodeURIComponent(session.owner)}`;
|
|
52
|
+
|
|
53
|
+
const handleUnpublish = () => {
|
|
54
|
+
confirm(tr('portfolio.builder.unpublishConfirm', 'Unpublish your portfolio? The public page stops working until you publish again.'), async () => {
|
|
55
|
+
try {
|
|
56
|
+
await apiPut('/v1/portfolio/config', { ...cfg, enabled: false, tags: ['portfolio'] });
|
|
57
|
+
showToast?.(tr('portfolio.builder.unpublished', 'Portfolio unpublished'));
|
|
58
|
+
navigate('/v1/portfolio');
|
|
59
|
+
} catch (e) { showToast?.(e.message || 'Failed', true); }
|
|
60
|
+
}, { danger: true });
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
return html`
|
|
64
|
+
<div class="tab-content">
|
|
65
|
+
<div class="section-title">${t('portfolio.builder.heading')}</div>
|
|
66
|
+
<div class="section-desc">${t('portfolio.builder.enabled')}</div>
|
|
67
|
+
|
|
68
|
+
<div class="card">
|
|
69
|
+
<div class="mem-item">
|
|
70
|
+
<span class="mem-key">${tr('portfolio.builder.publicUrl', 'Public URL')}</span>
|
|
71
|
+
<span class="access-copy-val">
|
|
72
|
+
<a href=${url} target="_blank">${url}</a>
|
|
73
|
+
<${CopyButton} text=${url} className="btn-ghost btn-sm" label="📋"
|
|
74
|
+
onCopied=${() => showToast?.(t('profile.access.copied') || 'Copied')} />
|
|
75
|
+
</span>
|
|
76
|
+
</div>
|
|
77
|
+
${cfg.publishedAt && html`
|
|
78
|
+
<div class="mem-item">
|
|
79
|
+
<span class="mem-key">${tr('portfolio.builder.lastUpdated', 'Last updated')}</span>
|
|
80
|
+
<span>${new Date(cfg.publishedAt).toLocaleString(getLocale() === 'fi' ? 'fi-FI' : undefined)}</span>
|
|
81
|
+
</div>
|
|
82
|
+
`}
|
|
83
|
+
${cfg.htmlSizeKb && html`
|
|
84
|
+
<div class="mem-item">
|
|
85
|
+
<span class="mem-key">${tr('portfolio.builder.sizeLabel', 'Size')}</span>
|
|
86
|
+
<span>${cfg.htmlSizeKb} KB</span>
|
|
87
|
+
</div>
|
|
88
|
+
`}
|
|
89
|
+
<div class="card-actions">
|
|
90
|
+
<a class="btn-outline btn-sm" href=${url} target="_blank">${tr('portfolio.builder.visitBtn', 'Visit')}</a>
|
|
91
|
+
<button class="btn-outline btn-sm" onClick=${() => navigate('/v1/portfolio')}>${tr('portfolio.builder.editBtn', 'Edit in builder')}</button>
|
|
92
|
+
<button class="btn-danger btn-sm" onClick=${handleUnpublish}>${tr('portfolio.builder.unpublish', 'Unpublish')}</button>
|
|
93
|
+
</div>
|
|
94
|
+
</div>
|
|
95
|
+
<${ConfirmUI} />
|
|
96
|
+
</div>
|
|
97
|
+
`;
|
|
98
|
+
}
|
|
@@ -58,6 +58,7 @@ import SecurityTab from './profile/security-tab.js';
|
|
|
58
58
|
import EmailTab from './profile/email-tab.js';
|
|
59
59
|
import KnowledgeTab from './profile/knowledge-tab.js';
|
|
60
60
|
import OrganismsTab from './profile/organisms-tab.js';
|
|
61
|
+
import OffersTab from './profile/offers-tab.js';
|
|
61
62
|
import NotificationsTab from './profile/notifications-tab.js';
|
|
62
63
|
import GeneratorTab from './profile/generator-tab.js';
|
|
63
64
|
import FoundryTab from './profile/foundry-tab.js';
|
|
@@ -72,6 +73,7 @@ import SchedulerTab from './profile/scheduler-tab.js';
|
|
|
72
73
|
const TABS = [
|
|
73
74
|
{ id: 'portfolio', key: 'portfolio.tabLabel', component: PortfolioTab, minTier: 'active' },
|
|
74
75
|
{ id: 'agents', key: 'profile.tabs.agents', component: AgentsTab, minTier: 'active' },
|
|
76
|
+
{ id: 'offers', key: 'profile.tabs.offers', component: OffersTab, minTier: 'active' },
|
|
75
77
|
{ id: 'scheduler', key: 'profile.tabs.scheduler', component: SchedulerTab, minTier: 'active' },
|
|
76
78
|
{ id: 'chatsessions', key: 'profile.tabs.chatSessions', component: ChatSessionsTab, minTier: 'active' },
|
|
77
79
|
{ id: 'mcp', key: 'profile.tabs.mcp', component: McpTab, minTier: 'active' },
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file start-flows.js
|
|
3
|
+
* @description Data + logic for the /v1/start diagnosis page: entrance configs
|
|
4
|
+
* (per LinkedIn-post angle, selected via ?from=), diagnostic questions with
|
|
5
|
+
* scores, verdict tiers, the build questions (path / tool / need / automation)
|
|
6
|
+
* and the playbook builder that turns the answers into a checklist with real,
|
|
7
|
+
* verified attach commands (sourced from connect.html — do not invent new ones).
|
|
8
|
+
* All user-visible copy is i18n key + English fallback pairs `{ k, f }`,
|
|
9
|
+
* resolved by the view; Finnish lives in locales/fi.json.
|
|
10
|
+
* @structure ENTRANCES, BUILD_QUESTIONS, scoreAnswers(), tierFor(),
|
|
11
|
+
* buildPlaybook(), playbookToMarkdown()
|
|
12
|
+
* @usage import { ENTRANCES, ... } from './start-flows.js' (relative — no importmap entry)
|
|
13
|
+
* @version-history
|
|
14
|
+
* v1.0.0 — 2026-06-11 — Initial: jira + default entrances, 4 verdict tiers,
|
|
15
|
+
* hosted/self/help playbooks (Spider rules: diagnosis framing, shareable
|
|
16
|
+
* verdict, ends in a copyable prompt, no email gate).
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
// ── Diagnostic questions (shared by jira + default entrances) ──
|
|
20
|
+
|
|
21
|
+
const Q_IDEA_FATE = {
|
|
22
|
+
id: 'ideaFate',
|
|
23
|
+
title: { k: 'start.q1.title', f: 'When someone drops a good idea, what happens to it?' },
|
|
24
|
+
options: [
|
|
25
|
+
{ id: 'prod', score: 3, text: { k: 'start.q1.prod', f: "It's in production before the evening news" } },
|
|
26
|
+
{ id: 'backlog', score: 1, text: { k: 'start.q1.backlog', f: 'Into the backlog. Groomed. Maybe Q3.' } },
|
|
27
|
+
{ id: 'grave', score: 0, text: { k: 'start.q1.grave', f: "It's taken to JIRA to die. Ceremonies twice a week." } },
|
|
28
|
+
{ id: 'chaos', score: 0, text: { k: 'start.q1.chaos', f: 'Nowhere. Someone might remember it next week.' } },
|
|
29
|
+
],
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const Q_AI_MEMORY = {
|
|
33
|
+
id: 'aiMemory',
|
|
34
|
+
title: { k: 'start.q2.title', f: 'What does your AI remember about yesterday?' },
|
|
35
|
+
options: [
|
|
36
|
+
{ id: 'all', score: 3, text: { k: 'start.q2.all', f: 'Everything: it has a memory, a task queue and a work history' } },
|
|
37
|
+
{ id: 'paste', score: 1, text: { k: 'start.q2.paste', f: 'Whatever I bother to copy-paste into the chat' } },
|
|
38
|
+
{ id: 'none', score: 0, text: { k: 'start.q2.none', f: 'Nothing. I reintroduce myself to it every morning.' } },
|
|
39
|
+
],
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const Q_AGENT_HOME = {
|
|
43
|
+
id: 'agentHome',
|
|
44
|
+
title: { k: 'start.q3.title', f: "Where does your AI's work live?" },
|
|
45
|
+
options: [
|
|
46
|
+
{ id: 'home', score: 3, text: { k: 'start.q3.home', f: 'In a shared home where the team and other agents can find it' } },
|
|
47
|
+
{ id: 'disk', score: 1, text: { k: 'start.q3.disk', f: 'On my machine, in a folder called final_v3_REAL' } },
|
|
48
|
+
{ id: 'scattered', score: 0, text: { k: 'start.q3.scattered', f: 'Scattered across chat histories. Nobody digs those up.' } },
|
|
49
|
+
],
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
// ── Entrances — one per LinkedIn-post angle (?from=jira|sotku|omistus).
|
|
53
|
+
// sotku/omistus copy is not written yet; they fall back to the neutral
|
|
54
|
+
// entrance so the links can ship before their entrance copy does. ──
|
|
55
|
+
|
|
56
|
+
export const ENTRANCES = {
|
|
57
|
+
jira: {
|
|
58
|
+
id: 'jira',
|
|
59
|
+
title: { k: 'start.jira.title', f: 'How dead is your JIRA?' },
|
|
60
|
+
sub: { k: 'start.jira.sub', f: 'The diagnosis takes 90 seconds. No email gate, no form, no sales call. At the end you get a verdict and a playbook for trying the cure yourself.' },
|
|
61
|
+
questions: [Q_IDEA_FATE, Q_AI_MEMORY, Q_AGENT_HOME],
|
|
62
|
+
},
|
|
63
|
+
default: {
|
|
64
|
+
id: 'default',
|
|
65
|
+
title: { k: 'start.def.title', f: 'What shape are your agents in?' },
|
|
66
|
+
sub: { k: 'start.def.sub', f: 'The diagnosis takes 90 seconds. No email gate, no form, no sales call. At the end you get a verdict and a playbook for trying the cure yourself.' },
|
|
67
|
+
questions: [Q_IDEA_FATE, Q_AI_MEMORY, Q_AGENT_HOME],
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
export function entranceFor(slug) {
|
|
72
|
+
return ENTRANCES[slug] || ENTRANCES.default;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// ── Verdict tiers (max score 9) ──
|
|
76
|
+
|
|
77
|
+
export const TIERS = [
|
|
78
|
+
{ id: 't0', min: 0, name: { k: 'start.t0.name', f: 'Level 0: The Graveyard' },
|
|
79
|
+
desc: { k: 'start.t0.desc', f: 'Your ideas die ceremonially and your AI has amnesia. You pay for tools that remember nothing and a process that ships nothing. Good news: the only way is up, and the climb starts below.' } },
|
|
80
|
+
{ id: 't1', min: 3, name: { k: 'start.t1.name', f: 'Level 1: In Therapy' },
|
|
81
|
+
desc: { k: 'start.t1.desc', f: 'Your AI goes to therapy, not to work: you carry its context in every morning and it forgets everything by night. You hired a brilliant employee and gave it amnesia.' } },
|
|
82
|
+
{ id: 't2', min: 6, name: { k: 'start.t2.name', f: 'Level 2: Working Without a Manager' },
|
|
83
|
+
desc: { k: 'start.t2.desc', f: 'You have agents and they do things, but nothing sticks and nobody runs the whole. Missing: a home, a memory and a manager.' } },
|
|
84
|
+
{ id: 't3', min: 8, name: { k: 'start.t3.name', f: 'Level 3: Goes to Work' },
|
|
85
|
+
desc: { k: 'start.t3.desc', f: 'Your agents actually go to work. Rare. Congratulations. Your playbook shows how to scale this and own the whole chain yourself.' } },
|
|
86
|
+
];
|
|
87
|
+
|
|
88
|
+
export function scoreAnswers(questions, answers) {
|
|
89
|
+
let sum = 0;
|
|
90
|
+
for (const q of questions) {
|
|
91
|
+
const opt = q.options.find((o) => o.id === answers[q.id]);
|
|
92
|
+
if (opt) sum += opt.score;
|
|
93
|
+
}
|
|
94
|
+
return sum;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export function tierFor(score) {
|
|
98
|
+
let tier = TIERS[0];
|
|
99
|
+
for (const t of TIERS) if (score >= t.min) tier = t;
|
|
100
|
+
return tier;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// ── Build questions (asked after the verdict; they become the playbook) ──
|
|
104
|
+
|
|
105
|
+
export const Q_PATH = {
|
|
106
|
+
id: 'path',
|
|
107
|
+
title: { k: 'start.q4.title', f: 'How do you want to try this?' },
|
|
108
|
+
options: [
|
|
109
|
+
{ id: 'hosted', text: { k: 'start.q4.hosted', f: 'Try it first: hosted aimeat.io, nothing to install' } },
|
|
110
|
+
{ id: 'self', text: { k: 'start.q4.self', f: "I'll build it myself: my own node on my own hardware (MIT, free)" } },
|
|
111
|
+
{ id: 'help', text: { k: 'start.q4.help', f: 'I want help: you do it ($$)' } },
|
|
112
|
+
],
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
export const Q_TOOL = {
|
|
116
|
+
id: 'tool',
|
|
117
|
+
title: { k: 'start.q5.title', f: 'What tool does your AI live in?' },
|
|
118
|
+
options: [
|
|
119
|
+
{ id: 'claude', text: { k: 'start.q5.claude', f: 'Claude (web or desktop)' } },
|
|
120
|
+
{ id: 'claudeCode', text: { k: 'start.q5.claudeCode', f: 'Claude Code (terminal)' } },
|
|
121
|
+
{ id: 'copilot', text: { k: 'start.q5.copilot', f: 'VS Code + Copilot' } },
|
|
122
|
+
{ id: 'cursor', text: { k: 'start.q5.cursor', f: 'Cursor' } },
|
|
123
|
+
{ id: 'otherChat', text: { k: 'start.q5.otherChat', f: 'Some other chat (ChatGPT, Gemini, …)' } },
|
|
124
|
+
],
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
export const Q_NEED = {
|
|
128
|
+
id: 'need',
|
|
129
|
+
title: { k: 'start.q5n.title', f: 'What would you want agents to handle first?' },
|
|
130
|
+
options: [
|
|
131
|
+
{ id: 'code', text: { k: 'start.q5n.code', f: 'Code and PRs' } },
|
|
132
|
+
{ id: 'docs', text: { k: 'start.q5n.docs', f: 'Documentation and a home for knowledge' } },
|
|
133
|
+
{ id: 'watch', text: { k: 'start.q5n.watch', f: "Monitoring and reporting: what's happening, every morning" } },
|
|
134
|
+
{ id: 'unsure', text: { k: 'start.q5n.unsure', f: "I don't know yet, that's why I'm asking" } },
|
|
135
|
+
],
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
export const Q_AUTOMATION = {
|
|
139
|
+
id: 'automation',
|
|
140
|
+
title: { k: 'start.q6.title', f: 'Does the work happen by itself, or when you say so?' },
|
|
141
|
+
options: [
|
|
142
|
+
{ id: 'manual', text: { k: 'start.q6.manual', f: 'When I say so: the AI helps when I ask' } },
|
|
143
|
+
{ id: 'routine', text: { k: 'start.q6.routine', f: 'An agreed routine: the AI checks the queue regularly' } },
|
|
144
|
+
{ id: 'continuous', text: { k: 'start.q6.continuous', f: 'By itself: the AI works while I sleep' } },
|
|
145
|
+
],
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
/** The build-phase question sequence for a chosen path. The help path skips
|
|
149
|
+
* the automation question — its playbook never uses the answer, and we don't
|
|
150
|
+
* ask what we don't use. */
|
|
151
|
+
export function buildQuestionsFor(answers) {
|
|
152
|
+
if (answers.path === 'help') return [Q_PATH, Q_NEED];
|
|
153
|
+
return [Q_PATH, Q_TOOL, Q_AUTOMATION];
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// ── Playbook builder ──
|
|
157
|
+
// Returns sections: { title: {k,f}, items: [{ text?: {k,f}, code?: string,
|
|
158
|
+
// prompt?: {k,f}, link?: { href, label: {k,f} } }] }.
|
|
159
|
+
// Commands come from connect.html — keep them verbatim, do not invent.
|
|
160
|
+
|
|
161
|
+
export function buildPlaybook(answers, mcpUrl) {
|
|
162
|
+
const { path, tool, need, automation } = answers;
|
|
163
|
+
const sections = [];
|
|
164
|
+
|
|
165
|
+
if (path === 'help') {
|
|
166
|
+
const needText = { k: `start.q5n.${need}`, f: '' };
|
|
167
|
+
sections.push({
|
|
168
|
+
title: { k: 'start.pb.h1.title', f: 'Step 1: Tell us what you need' },
|
|
169
|
+
items: [
|
|
170
|
+
{ text: { k: 'start.pb.h1.a', f: "Send a message to jouni.miikki@aimeat.io. Subject 'Agents', body: your pick below. You'll get an honest assessment: what to build yourself, what to buy." } },
|
|
171
|
+
{ text: needText, quote: true },
|
|
172
|
+
],
|
|
173
|
+
});
|
|
174
|
+
sections.push({
|
|
175
|
+
title: { k: 'start.pb.h2.title', f: 'Step 2: See the pricing' },
|
|
176
|
+
items: [
|
|
177
|
+
{ text: { k: 'start.pb.h2.a', f: 'Self-serve tiers and done-for-you packages:' },
|
|
178
|
+
link: { href: '/v1/pricing', label: { k: 'start.pb.h2.link', f: 'See pricing →' } } },
|
|
179
|
+
],
|
|
180
|
+
});
|
|
181
|
+
sections.push({
|
|
182
|
+
title: { k: 'start.pb.h3.title', f: 'Step 3: Try it meanwhile' },
|
|
183
|
+
items: [
|
|
184
|
+
{ text: { k: 'start.pb.h3.a', f: "Nothing stops you from seeing it yourself before paying anything: create a free account and connect your own AI." },
|
|
185
|
+
link: { href: '/v1/portal', label: { k: 'start.pb.h3.link', f: 'Create a free account →' } } },
|
|
186
|
+
],
|
|
187
|
+
});
|
|
188
|
+
return sections;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Step 1 — account / own node
|
|
192
|
+
if (path === 'self') {
|
|
193
|
+
sections.push({
|
|
194
|
+
title: { k: 'start.pb.s1self.title', f: 'Step 1: Install your own node' },
|
|
195
|
+
items: [
|
|
196
|
+
{ text: { k: 'start.pb.s1self.a', f: 'Clone and run the init wizard, it asks the rest:' },
|
|
197
|
+
code: 'git clone https://github.com/miikkij/aimeat-protocol\ncd aimeat-protocol\npnpm install && pnpm aimeat init' },
|
|
198
|
+
{ text: { k: 'start.pb.s1self.b', f: 'Everything below is then done against your own node: replace the aimeat.io address with yours. Your hardware, your keys, your data.' } },
|
|
199
|
+
],
|
|
200
|
+
});
|
|
201
|
+
} else {
|
|
202
|
+
sections.push({
|
|
203
|
+
title: { k: 'start.pb.s1.title', f: 'Step 1: An account and a home' },
|
|
204
|
+
items: [
|
|
205
|
+
{ text: { k: 'start.pb.s1.a', f: "Create a free account. No card, no payment. The account is your agent's home and your permission for it to exist." },
|
|
206
|
+
link: { href: '/v1/portal', label: { k: 'start.pb.s1.link', f: 'Create account →' } } },
|
|
207
|
+
],
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Step 2 — connect the AI (per tool, commands verbatim from connect.html)
|
|
212
|
+
const connect = { title: { k: 'start.pb.s2.title', f: 'Step 2: Connect your AI' }, items: [] };
|
|
213
|
+
if (tool === 'claude') {
|
|
214
|
+
connect.items.push({ text: { k: 'start.pb.s2.claude', f: 'Open Claude: Settings → Connectors → Add custom connector, and paste this address:' }, code: mcpUrl });
|
|
215
|
+
connect.items.push({ text: { k: 'start.pb.s2.claudeNote', f: 'The free claude.ai tier fits one custom connector, and that is enough for this.' } });
|
|
216
|
+
} else if (tool === 'claudeCode') {
|
|
217
|
+
connect.items.push({ text: { k: 'start.pb.s2.cli', f: 'Run this in a terminal:' }, code: `claude mcp add aimeat --transport http ${mcpUrl}` });
|
|
218
|
+
} else if (tool === 'copilot') {
|
|
219
|
+
connect.items.push({ text: { k: 'start.pb.s2.cli', f: 'Run this in a terminal:' }, code: `code --add-mcp '{"name":"aimeat","url":"${mcpUrl}"}'` });
|
|
220
|
+
} else if (tool === 'cursor') {
|
|
221
|
+
connect.items.push({ text: { k: 'start.pb.s2.cursor', f: 'Use the 1-click install on the connect page:' },
|
|
222
|
+
link: { href: '/v1/connect', label: { k: 'start.pb.s2.cursorLink', f: 'Open the connect page →' } } });
|
|
223
|
+
} else {
|
|
224
|
+
connect.items.push({ text: { k: 'start.pb.s2.other', f: 'If your tool supports Connectors / custom MCP, paste this address into it:' }, code: mcpUrl });
|
|
225
|
+
connect.items.push({ text: { k: 'start.pb.s2.otherNote', f: "No MCP support? AIMEAT also works prompt-driven: the app composes the prompts, you carry them to your chat and bring the answers back. Free, and works with any AI." } });
|
|
226
|
+
}
|
|
227
|
+
connect.items.push({ text: { k: 'start.pb.s2.approve', f: 'When the AI asks for access, approve the agent in your profile and pick its permissions. No agent is ever created without your approval.' } });
|
|
228
|
+
sections.push(connect);
|
|
229
|
+
|
|
230
|
+
// Step 3 — startup prompt (the conversion: the AI creates the first organism)
|
|
231
|
+
sections.push({
|
|
232
|
+
title: { k: 'start.pb.s3.title', f: 'Step 3: Put your AI to work' },
|
|
233
|
+
items: [
|
|
234
|
+
{ text: { k: 'start.pb.s3.lead', f: 'Copy this prompt to your AI:' },
|
|
235
|
+
prompt: { k: 'start.pb.s3.prompt', f: "You are connected to the AIMEAT platform via MCP. Create an organism for me called 'My Office' with a workspace. Then read what's there, ask me what I want you to do, and record my answer as the first task in the workspace. Confirm when the task is saved." } },
|
|
236
|
+
{ text: { k: 'start.pb.s3.check', f: 'Check your profile: the task appeared. You just watched the whole loop: idea → memory → task queue. No ceremonies.' } },
|
|
237
|
+
],
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
// Step 4 — first real job
|
|
241
|
+
sections.push({
|
|
242
|
+
title: { k: 'start.pb.s4.title', f: 'Step 4: The first real job' },
|
|
243
|
+
items: [
|
|
244
|
+
{ text: { k: 'start.pb.s4.a', f: 'Give the AI one small, real task from your day: file a note, turn a project status into a document, sort a list.' } },
|
|
245
|
+
{ text: { k: 'start.pb.s4.b', f: 'Everything the AI writes stays with you. The memory, the documents and the tasks are yours, not an AI vendor’s.' } },
|
|
246
|
+
],
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
// Step 5 — automation level
|
|
250
|
+
const auto = { title: { k: 'start.pb.s5.title', f: 'Step 5: Automation level' }, items: [] };
|
|
251
|
+
if (automation === 'continuous') {
|
|
252
|
+
auto.items.push({ text: { k: 'start.pb.s5.continuous', f: 'You picked: by itself. A connector serve daemon runs your agent in the background and tasks arrive as pushes, no polling. A fleet of 33 agents runs in production on this model.' },
|
|
253
|
+
link: { href: '/v1/connect', label: { k: 'start.pb.s5.link', f: 'Connector docs →' } } });
|
|
254
|
+
} else if (automation === 'routine') {
|
|
255
|
+
auto.items.push({ text: { k: 'start.pb.s5.routine', f: 'You picked: an agreed routine. Agree a scheduled check with your AI that reads the task queue regularly. AIMEAT’s own schedules work as the wake-up call and reminder.' } });
|
|
256
|
+
} else {
|
|
257
|
+
auto.items.push({ text: { k: 'start.pb.s5.manual', f: "You picked: when I say so. Start every session with the prompt: 'Read the open tasks in the workspace and handle them.' You are the scheduler, and that is enough to start." } });
|
|
258
|
+
}
|
|
259
|
+
if (automation !== 'continuous') {
|
|
260
|
+
auto.items.push({ text: { k: 'start.pb.s5.upgrade', f: 'You can raise the level any time: by hand → routine → daemon. The playbook does not expire.' } });
|
|
261
|
+
}
|
|
262
|
+
sections.push(auto);
|
|
263
|
+
|
|
264
|
+
return sections;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/** Plain-markdown rendering of the playbook for the copy button. */
|
|
268
|
+
export function playbookToMarkdown(sections, trr, titleText) {
|
|
269
|
+
const lines = [`# ${titleText}`, ''];
|
|
270
|
+
for (const s of sections) {
|
|
271
|
+
lines.push(`## ${trr(s.title)}`, '');
|
|
272
|
+
for (const it of s.items) {
|
|
273
|
+
if (it.text) lines.push(it.quote ? `> ${trr(it.text)}` : `- [ ] ${trr(it.text)}`);
|
|
274
|
+
if (it.code) lines.push('', '```', it.code, '```', '');
|
|
275
|
+
if (it.prompt) lines.push('', '> ' + trr(it.prompt), '');
|
|
276
|
+
if (it.link) lines.push(` ${trr(it.link.label)} ${it.link.href}`);
|
|
277
|
+
}
|
|
278
|
+
lines.push('');
|
|
279
|
+
}
|
|
280
|
+
return lines.join('\n');
|
|
281
|
+
}
|