aimeat 1.23.1 → 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 +674 -125
- package/dist/locales/fi.json +712 -163
- package/dist/public/components/ContactCard.js +28 -0
- package/dist/public/css/theme.css +25 -10
- 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 +216 -7
- 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/organisms.js +28 -5
- 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 +1330 -485
- 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/cli/connect/mcp/tools/organisms.d.ts +3 -0
- package/dist/src/cli/connect/mcp/tools/organisms.d.ts.map +1 -1
- package/dist/src/cli/connect/mcp/tools/organisms.js +20 -3
- package/dist/src/cli/connect/mcp/tools/organisms.js.map +1 -1
- package/dist/src/cli/connect/tool-call.d.ts.map +1 -1
- package/dist/src/cli/connect/tool-call.js +20 -1
- package/dist/src/cli/connect/tool-call.js.map +1 -1
- 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/mcp/organisms.d.ts +3 -0
- package/dist/src/mcp/organisms.d.ts.map +1 -1
- package/dist/src/mcp/organisms.js +18 -0
- package/dist/src/mcp/organisms.js.map +1 -1
- 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/organisms.d.ts.map +1 -1
- package/dist/src/routes/organisms.js +27 -5
- package/dist/src/routes/organisms.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/services/schema-validator.d.ts.map +1 -1
- package/dist/src/services/schema-validator.js +12 -0
- package/dist/src/services/schema-validator.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
|
@@ -3,6 +3,12 @@
|
|
|
3
3
|
* @description Portfolio view — builder (select content, generate AI prompt,
|
|
4
4
|
* upload HTML) and public viewer (sandboxed iframe render).
|
|
5
5
|
* @version-history
|
|
6
|
+
* v1.2.0 — 2026-06-10 — Workflow round: ①②③ flow banner explains the AI-chat round-trip;
|
|
7
|
+
* group summaries show selected/total + select-all checkbox; Generate disabled with a hint
|
|
8
|
+
* when nothing is selected; "Custom" type and custom auth-gate open describe-textareas that
|
|
9
|
+
* feed the prompt; prompt output shows a character count; step 5 shows the publish URL,
|
|
10
|
+
* gains a sandboxed preview (same allow-scripts srcdoc sandbox as the public viewer) and an
|
|
11
|
+
* Unpublish in the published banner; bottom button says Profile (where it actually goes).
|
|
6
12
|
* v1.1.0 — 2026-06-02 — Migrate bespoke prompt-copy button to canonical CopyButton component
|
|
7
13
|
*/
|
|
8
14
|
import { h } from 'preact';
|
|
@@ -11,6 +17,10 @@ import htm from 'htm';
|
|
|
11
17
|
const html = htm.bind(h);
|
|
12
18
|
import { t } from '/js/i18n.js';
|
|
13
19
|
import { escHtml, handleImgError } from '/js/utils.js';
|
|
20
|
+
|
|
21
|
+
// t() echoes the key when a translation is missing (e.g. a server still serving
|
|
22
|
+
// pre-update locales) — fall back to readable English instead of raw keys.
|
|
23
|
+
const tr = (key, fallback) => { const v = t(key); return v && v !== key ? v : fallback; };
|
|
14
24
|
import { apiGet, apiPut } from '/js/api.js';
|
|
15
25
|
import TagCloud from '/js/components/tag-cloud.js';
|
|
16
26
|
import { CopyButton } from '/components/CopyButton.js';
|
|
@@ -65,7 +75,7 @@ function formatMemoryKey(key) {
|
|
|
65
75
|
}
|
|
66
76
|
|
|
67
77
|
/* ── Prompt Builder ── */
|
|
68
|
-
function buildPortfolioPrompt({ session, catalog, selectedImages, selectedApps, selectedBoards, selectedCortex, selectedMemories, portfolioType, designStyle, authGates }) {
|
|
78
|
+
function buildPortfolioPrompt({ session, catalog, selectedImages, selectedApps, selectedBoards, selectedCortex, selectedMemories, portfolioType, designStyle, authGates, customTypeDesc, customGateDesc }) {
|
|
69
79
|
const ghii = session.ghii || (session.owner + '@unknown');
|
|
70
80
|
const url = NODE_URL;
|
|
71
81
|
|
|
@@ -132,13 +142,13 @@ The user wants to create a personal portfolio website. Generate a single, self-c
|
|
|
132
142
|
|
|
133
143
|
prompt += `
|
|
134
144
|
## Portfolio Requirements
|
|
135
|
-
- Type: ${typeLabels[portfolioType] || portfolioType}
|
|
145
|
+
- Type: ${typeLabels[portfolioType] || portfolioType}${portfolioType === 'custom' && customTypeDesc ? ` — ${customTypeDesc}` : ''}
|
|
136
146
|
- Design Style: ${styleLabels[designStyle] || designStyle}
|
|
137
147
|
`;
|
|
138
148
|
|
|
139
149
|
// Auth-gated sections
|
|
140
150
|
if (authGates.length > 0) {
|
|
141
|
-
const gateLabels = { contact: 'Contact information', projectDetails: 'Project details', downloads: 'Download links', custom: 'Custom sections (ask user)' };
|
|
151
|
+
const gateLabels = { contact: 'Contact information', projectDetails: 'Project details', downloads: 'Download links', custom: customGateDesc ? `Custom sections: ${customGateDesc}` : 'Custom sections (ask user)' };
|
|
142
152
|
prompt += `- Auth-gated sections (show only to logged-in viewers):\n`;
|
|
143
153
|
for (const gate of authGates) {
|
|
144
154
|
prompt += ` - ${gateLabels[gate] || gate}\n`;
|
|
@@ -282,6 +292,30 @@ function PortfolioBuilder({ session, navigate }) {
|
|
|
282
292
|
});
|
|
283
293
|
};
|
|
284
294
|
|
|
295
|
+
// Custom descriptions (step 2 "Custom" type / step 3 "Custom sections")
|
|
296
|
+
const [customTypeDesc, setCustomTypeDesc] = useState('');
|
|
297
|
+
const [customGateDesc, setCustomGateDesc] = useState('');
|
|
298
|
+
|
|
299
|
+
// Group <summary> with selected/total + a select-all checkbox. The checkbox click
|
|
300
|
+
// must not toggle the <details> open state (stopPropagation + preventDefault on the
|
|
301
|
+
// summary's default toggle is handled by stopping the click at the input).
|
|
302
|
+
const groupSummary = (label, ids, selectedSet, setter) => {
|
|
303
|
+
const selCount = ids.filter(id => selectedSet.has(id)).length;
|
|
304
|
+
const all = ids.length > 0 && selCount === ids.length;
|
|
305
|
+
return html`
|
|
306
|
+
<summary>
|
|
307
|
+
<input type="checkbox" class="portfolio-group-checkall" checked=${all}
|
|
308
|
+
title=${tr('portfolio.builder.selectAll', 'Select all')}
|
|
309
|
+
onClick=${(e) => e.stopPropagation()}
|
|
310
|
+
onChange=${() => setter(prev => {
|
|
311
|
+
const next = new Set(prev);
|
|
312
|
+
if (all) ids.forEach(i => next.delete(i)); else ids.forEach(i => next.add(i));
|
|
313
|
+
return next;
|
|
314
|
+
})} />
|
|
315
|
+
${label} — ${tr('portfolio.builder.selectedOf', '{sel}/{total} selected').replace('{sel}', String(selCount)).replace('{total}', String(ids.length))}
|
|
316
|
+
</summary>`;
|
|
317
|
+
};
|
|
318
|
+
|
|
285
319
|
// Generate prompt
|
|
286
320
|
const handleGenerate = () => {
|
|
287
321
|
if (!catalog) return;
|
|
@@ -302,6 +336,8 @@ function PortfolioBuilder({ session, navigate }) {
|
|
|
302
336
|
portfolioType,
|
|
303
337
|
designStyle,
|
|
304
338
|
authGates: [...authGates],
|
|
339
|
+
customTypeDesc: customTypeDesc.trim(),
|
|
340
|
+
customGateDesc: customGateDesc.trim(),
|
|
305
341
|
});
|
|
306
342
|
setGeneratedPrompt(prompt);
|
|
307
343
|
|
|
@@ -333,11 +369,12 @@ function PortfolioBuilder({ session, navigate }) {
|
|
|
333
369
|
|
|
334
370
|
// Paste HTML
|
|
335
371
|
const [pastedHtml, setPastedHtml] = useState('');
|
|
372
|
+
const [showPreview, setShowPreview] = useState(false);
|
|
336
373
|
|
|
337
374
|
const handlePublishPaste = async () => {
|
|
338
375
|
const text = pastedHtml.trim();
|
|
339
376
|
if (!text) {
|
|
340
|
-
setUploadStatus({ ok: false, msg:
|
|
377
|
+
setUploadStatus({ ok: false, msg: tr('portfolio.builder.pasteEmpty', 'Paste HTML content first') });
|
|
341
378
|
return;
|
|
342
379
|
}
|
|
343
380
|
await publishHtml(text);
|
|
@@ -384,6 +421,19 @@ function PortfolioBuilder({ session, navigate }) {
|
|
|
384
421
|
setUploading(false);
|
|
385
422
|
};
|
|
386
423
|
|
|
424
|
+
// Unpublish: flip enabled off — the public route 404s until the next publish.
|
|
425
|
+
const handleUnpublish = async () => {
|
|
426
|
+
setUploading(true);
|
|
427
|
+
try {
|
|
428
|
+
await apiPut('/v1/portfolio/config', { ...(existingConfig || {}), enabled: false, tags: ['portfolio'] });
|
|
429
|
+
setExistingConfig({ ...(existingConfig || {}), enabled: false });
|
|
430
|
+
setUploadStatus({ ok: true, msg: tr('portfolio.builder.unpublished', 'Portfolio unpublished') });
|
|
431
|
+
} catch (err) {
|
|
432
|
+
setUploadStatus({ ok: false, msg: err.message || 'Failed' });
|
|
433
|
+
}
|
|
434
|
+
setUploading(false);
|
|
435
|
+
};
|
|
436
|
+
|
|
387
437
|
// Upload HTML file (reads text from file, then publishes)
|
|
388
438
|
const handleUpload = async (file) => {
|
|
389
439
|
if (!file || !file.name.endsWith('.html')) {
|
|
@@ -408,16 +458,32 @@ function PortfolioBuilder({ session, navigate }) {
|
|
|
408
458
|
|
|
409
459
|
const hasContent = catalog && (catalog.images.length || catalog.apps.length || catalog.boards.length || catalog.cortex.length || catalog.memories.length);
|
|
410
460
|
|
|
461
|
+
const totalSelected = selectedImages.size + selectedApps.size + selectedBoards.size + selectedCortex.size + selectedMemories.size;
|
|
462
|
+
const publicUrl = `${NODE_URL}/v1/portfolio/${encodeURIComponent(session.owner)}`;
|
|
463
|
+
|
|
411
464
|
return html`
|
|
412
465
|
<div class="portfolio-container">
|
|
413
466
|
<h1>${t('portfolio.builder.heading')}</h1>
|
|
414
|
-
<p
|
|
467
|
+
<p class="portfolio-subtitle-text">${t('portfolio.builder.subtitle')}</p>
|
|
468
|
+
|
|
469
|
+
<!-- The loop is the whole point: AIMEAT composes a prompt, YOUR AI chat builds the
|
|
470
|
+
HTML, you paste it back. Without this banner step 1 gives no hint of steps 4-5. -->
|
|
471
|
+
<div class="portfolio-flow">
|
|
472
|
+
<span class="portfolio-flow-step"><span class="portfolio-flow-num">①</span> ${tr('portfolio.builder.flow1', 'Select your content and style')}</span>
|
|
473
|
+
<span class="portfolio-flow-arrow">→</span>
|
|
474
|
+
<span class="portfolio-flow-step"><span class="portfolio-flow-num">②</span> ${tr('portfolio.builder.flow2', 'Generate a prompt and run it in your AI chat (Claude, ChatGPT, …)')}</span>
|
|
475
|
+
<span class="portfolio-flow-arrow">→</span>
|
|
476
|
+
<span class="portfolio-flow-step"><span class="portfolio-flow-num">③</span> ${tr('portfolio.builder.flow3', 'Paste the resulting HTML back here and publish')}</span>
|
|
477
|
+
</div>
|
|
415
478
|
|
|
416
479
|
${existingConfig?.enabled && html`
|
|
417
|
-
<div
|
|
418
|
-
<span
|
|
480
|
+
<div class="portfolio-published-bar">
|
|
481
|
+
<span class="portfolio-published-dot">●</span>
|
|
419
482
|
<span>${t('portfolio.builder.enabled')}</span>
|
|
420
|
-
<a href
|
|
483
|
+
<a href=${publicUrl} target="_blank" class="portfolio-published-link">${t('portfolio.builder.viewPublic')}</a>
|
|
484
|
+
<button class="btn btn-ghost btn-sm" disabled=${uploading} onClick=${handleUnpublish}>
|
|
485
|
+
${tr('portfolio.builder.unpublish', 'Unpublish')}
|
|
486
|
+
</button>
|
|
421
487
|
</div>
|
|
422
488
|
`}
|
|
423
489
|
|
|
@@ -431,7 +497,7 @@ function PortfolioBuilder({ session, navigate }) {
|
|
|
431
497
|
|
|
432
498
|
${catalog.images.length > 0 && html`
|
|
433
499
|
<details class="portfolio-source-group" open>
|
|
434
|
-
|
|
500
|
+
${groupSummary(t('portfolio.builder.imagesGroup'), catalog.images.map(i => i.key), selectedImages, setSelectedImages)}
|
|
435
501
|
${(() => {
|
|
436
502
|
const allImgTags = new Set();
|
|
437
503
|
for (const img of catalog.images) {
|
|
@@ -470,7 +536,7 @@ function PortfolioBuilder({ session, navigate }) {
|
|
|
470
536
|
|
|
471
537
|
${catalog.apps.length > 0 && html`
|
|
472
538
|
<details class="portfolio-source-group">
|
|
473
|
-
|
|
539
|
+
${groupSummary(t('portfolio.builder.appsGroup'), catalog.apps.map(a => a.filename), selectedApps, setSelectedApps)}
|
|
474
540
|
<div class="portfolio-source-list">
|
|
475
541
|
${catalog.apps.map(app => html`
|
|
476
542
|
<div class="portfolio-source-item">
|
|
@@ -486,7 +552,7 @@ function PortfolioBuilder({ session, navigate }) {
|
|
|
486
552
|
|
|
487
553
|
${catalog.boards.length > 0 && html`
|
|
488
554
|
<details class="portfolio-source-group">
|
|
489
|
-
|
|
555
|
+
${groupSummary(t('portfolio.builder.boardsGroup'), catalog.boards.map(b => b.id), selectedBoards, setSelectedBoards)}
|
|
490
556
|
<div class="portfolio-source-list">
|
|
491
557
|
${catalog.boards.map(board => html`
|
|
492
558
|
<div class="portfolio-source-item">
|
|
@@ -502,7 +568,7 @@ function PortfolioBuilder({ session, navigate }) {
|
|
|
502
568
|
|
|
503
569
|
${catalog.cortex.length > 0 && html`
|
|
504
570
|
<details class="portfolio-source-group">
|
|
505
|
-
|
|
571
|
+
${groupSummary(t('portfolio.builder.cortexGroup'), catalog.cortex.map(c => c.name), selectedCortex, setSelectedCortex)}
|
|
506
572
|
<div class="portfolio-source-list">
|
|
507
573
|
${catalog.cortex.map(ext => html`
|
|
508
574
|
<div class="portfolio-source-item">
|
|
@@ -518,7 +584,7 @@ function PortfolioBuilder({ session, navigate }) {
|
|
|
518
584
|
|
|
519
585
|
${catalog.memories.length > 0 && html`
|
|
520
586
|
<details class="portfolio-source-group">
|
|
521
|
-
|
|
587
|
+
${groupSummary(t('portfolio.builder.memoriesGroup'), catalog.memories.map(m => m.key), selectedMemories, setSelectedMemories)}
|
|
522
588
|
${(() => {
|
|
523
589
|
const allMemTags = new Set();
|
|
524
590
|
for (const mem of catalog.memories) {
|
|
@@ -575,6 +641,11 @@ function PortfolioBuilder({ session, navigate }) {
|
|
|
575
641
|
</div>
|
|
576
642
|
`)}
|
|
577
643
|
</div>
|
|
644
|
+
${portfolioType === 'custom' && html`
|
|
645
|
+
<textarea class="portfolio-custom-desc" rows="3"
|
|
646
|
+
placeholder=${tr('portfolio.builder.customTypePlaceholder', 'Describe what kind of portfolio you want…')}
|
|
647
|
+
value=${customTypeDesc} onInput=${(e) => setCustomTypeDesc(e.target.value)}></textarea>
|
|
648
|
+
`}
|
|
578
649
|
|
|
579
650
|
<p style="color:var(--text-bright); font-size:0.95rem; margin:1.5rem 0 0.5rem;">${t('portfolio.builder.designStyle')}</p>
|
|
580
651
|
<div class="portfolio-options">
|
|
@@ -601,18 +672,29 @@ function PortfolioBuilder({ session, navigate }) {
|
|
|
601
672
|
</div>
|
|
602
673
|
`)}
|
|
603
674
|
</div>
|
|
675
|
+
${authGates.has('custom') && html`
|
|
676
|
+
<textarea class="portfolio-custom-desc" rows="2"
|
|
677
|
+
placeholder=${tr('portfolio.builder.customGatePlaceholder', 'Describe the custom auth-gated sections…')}
|
|
678
|
+
value=${customGateDesc} onInput=${(e) => setCustomGateDesc(e.target.value)}></textarea>
|
|
679
|
+
`}
|
|
604
680
|
</div>
|
|
605
681
|
|
|
606
682
|
<!-- Step 4: Generate Prompt -->
|
|
607
683
|
<div class="portfolio-step">
|
|
608
684
|
<h3><span class="portfolio-step-number">4</span> ${t('portfolio.builder.step4Title')}</h3>
|
|
609
685
|
|
|
610
|
-
<
|
|
611
|
-
|
|
612
|
-
|
|
686
|
+
<div class="portfolio-generate-row">
|
|
687
|
+
<button class="btn btn-primary" disabled=${totalSelected === 0} onClick=${handleGenerate}>
|
|
688
|
+
${t('portfolio.builder.generateBtn')}
|
|
689
|
+
</button>
|
|
690
|
+
${totalSelected === 0 && html`
|
|
691
|
+
<span class="portfolio-generate-hint">${tr('portfolio.builder.generateDisabledHint', 'Select at least one item in step 1')}</span>
|
|
692
|
+
`}
|
|
693
|
+
</div>
|
|
613
694
|
|
|
614
695
|
${generatedPrompt && html`
|
|
615
696
|
<div class="portfolio-prompt-output">${generatedPrompt}</div>
|
|
697
|
+
<div class="portfolio-prompt-meta">${tr('portfolio.builder.charCount', '{n} characters').replace('{n}', generatedPrompt.length.toLocaleString())}</div>
|
|
616
698
|
<div class="portfolio-prompt-actions">
|
|
617
699
|
<${CopyButton} text=${generatedPrompt} className="btn-primary"
|
|
618
700
|
label=${t('portfolio.builder.copyPrompt')} copiedLabel=${t('portfolio.builder.promptCopied')} />
|
|
@@ -637,6 +719,11 @@ function PortfolioBuilder({ session, navigate }) {
|
|
|
637
719
|
<div class="portfolio-step">
|
|
638
720
|
<h3><span class="portfolio-step-number">5</span> ${t('portfolio.builder.step5Title')}</h3>
|
|
639
721
|
|
|
722
|
+
<div class="portfolio-publish-target">
|
|
723
|
+
${tr('portfolio.builder.publishTarget', 'Will be published at:')}
|
|
724
|
+
<a href=${publicUrl} target="_blank" class="portfolio-publish-url">${publicUrl}</a>
|
|
725
|
+
</div>
|
|
726
|
+
|
|
640
727
|
<div class="portfolio-upload-zone ${dragover ? 'dragover' : ''}"
|
|
641
728
|
onClick=${() => fileInputRef.current?.click()}
|
|
642
729
|
onDragOver=${(e) => { e.preventDefault(); setDragover(true); }}
|
|
@@ -651,21 +738,31 @@ function PortfolioBuilder({ session, navigate }) {
|
|
|
651
738
|
|
|
652
739
|
<div style="display:flex; align-items:center; gap:0.75rem; margin:1rem 0; color:var(--text-dim); font-size:0.9rem;">
|
|
653
740
|
<hr style="flex:1; border:none; border-top:1px solid var(--border-dim);" />
|
|
654
|
-
<span>${
|
|
741
|
+
<span>${tr('portfolio.builder.orPaste', 'or paste HTML directly')}</span>
|
|
655
742
|
<hr style="flex:1; border:none; border-top:1px solid var(--border-dim);" />
|
|
656
743
|
</div>
|
|
657
744
|
|
|
658
745
|
<textarea class="portfolio-paste-area"
|
|
659
|
-
placeholder=${
|
|
746
|
+
placeholder=${tr('portfolio.builder.pasteHint', 'Paste your portfolio HTML here from AI chat...')}
|
|
660
747
|
value=${pastedHtml}
|
|
661
748
|
onInput=${(e) => setPastedHtml(e.target.value)}
|
|
662
749
|
rows="6"
|
|
663
750
|
></textarea>
|
|
664
751
|
${pastedHtml.trim() && html`
|
|
665
|
-
<
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
752
|
+
<div class="portfolio-paste-actions">
|
|
753
|
+
<button class="btn btn-primary"
|
|
754
|
+
onClick=${handlePublishPaste} disabled=${uploading}>
|
|
755
|
+
${uploading ? '...' : tr('portfolio.builder.publishPaste', 'Publish pasted HTML')}
|
|
756
|
+
</button>
|
|
757
|
+
<button class="btn btn-ghost" onClick=${() => setShowPreview(p => !p)}>
|
|
758
|
+
${showPreview ? tr('portfolio.builder.closePreview', 'Close preview') : tr('portfolio.builder.previewBtn', 'Preview')}
|
|
759
|
+
</button>
|
|
760
|
+
</div>
|
|
761
|
+
`}
|
|
762
|
+
${showPreview && pastedHtml.trim() && html`
|
|
763
|
+
<!-- Same sandbox as the public viewer: opaque origin, no session access. -->
|
|
764
|
+
<iframe class="portfolio-viewer-frame portfolio-preview-frame" srcdoc=${pastedHtml}
|
|
765
|
+
sandbox="allow-scripts"></iframe>
|
|
669
766
|
`}
|
|
670
767
|
|
|
671
768
|
${uploadStatus && html`
|
|
@@ -682,7 +779,7 @@ function PortfolioBuilder({ session, navigate }) {
|
|
|
682
779
|
|
|
683
780
|
<div style="margin-top:2rem;">
|
|
684
781
|
<button class="btn btn-ghost" onClick=${() => navigate('/v1/profile')}>
|
|
685
|
-
${
|
|
782
|
+
← ${tr('portfolio.builder.backToProfile', 'Profile')}
|
|
686
783
|
</button>
|
|
687
784
|
</div>
|
|
688
785
|
</div>
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file pricing.js
|
|
3
|
+
* @description Pricing ladder: Try it (0 €) → Own node (49 €/mo) → Own node Pro (99 €/mo)
|
|
4
|
+
* → Done for you (setup + monthly) → Enterprise (contact). No payment integration at
|
|
5
|
+
* this stage — CTAs are mailto links. #packages anchors the Done-for-you section
|
|
6
|
+
* (landing path card C links here). Short sentences, no hype adjectives.
|
|
7
|
+
* @usage routed at /v1/pricing by spa.html
|
|
8
|
+
* @version-history
|
|
9
|
+
* v1.0.0 — 2026-06-10 — Initial pricing page (landing/portal split).
|
|
10
|
+
*/
|
|
11
|
+
import { h } from 'preact';
|
|
12
|
+
import { useEffect } from 'preact/hooks';
|
|
13
|
+
import htm from 'htm';
|
|
14
|
+
const html = htm.bind(h);
|
|
15
|
+
import { t } from '/js/i18n.js';
|
|
16
|
+
import { ContactCard } from '/components/ContactCard.js';
|
|
17
|
+
|
|
18
|
+
const tr = (key, fallback) => { const v = t(key); return v && v !== key ? v : fallback; };
|
|
19
|
+
|
|
20
|
+
const CONTACT = 'mailto:jouni.miikki@aimeat.io';
|
|
21
|
+
|
|
22
|
+
export default function Pricing() {
|
|
23
|
+
// Honour the #packages anchor on arrival.
|
|
24
|
+
useEffect(() => {
|
|
25
|
+
if (window.location.hash) {
|
|
26
|
+
setTimeout(() => document.getElementById(window.location.hash.slice(1))?.scrollIntoView({ behavior: 'smooth' }), 200);
|
|
27
|
+
}
|
|
28
|
+
}, []);
|
|
29
|
+
|
|
30
|
+
const tier = (id, name, price, lines, ctaLabel, ctaHref, hot) => html`
|
|
31
|
+
<div class="ld-tier${hot ? ' ld-tier--hot' : ''}" id=${id || null}>
|
|
32
|
+
<h3 class="ld-tier-name">${name}</h3>
|
|
33
|
+
<div class="ld-tier-price">${price}</div>
|
|
34
|
+
<ul class="ld-tier-lines">${lines.map(l => html`<li key=${l}>${l}</li>`)}</ul>
|
|
35
|
+
<a class="ld-path-cta" href=${ctaHref}>${ctaLabel}</a>
|
|
36
|
+
</div>
|
|
37
|
+
`;
|
|
38
|
+
|
|
39
|
+
return html`
|
|
40
|
+
<div class="ld ld-pricing">
|
|
41
|
+
<h1 class="ld-h1">${tr('pricing.title', 'Pricing')}</h1>
|
|
42
|
+
<p class="ld-hero-sub">${tr('pricing.sub', 'Start free. Move to your own node when the data should be yours.')}</p>
|
|
43
|
+
|
|
44
|
+
<div class="ld-tiers">
|
|
45
|
+
${tier('try-it', tr('pricing.tryName', 'Try it'), '0 €', [
|
|
46
|
+
tr('pricing.try1', 'On the genesis node.'),
|
|
47
|
+
tr('pricing.try2', 'Morsels, public apps, the prompt loop.'),
|
|
48
|
+
], tr('pricing.tryCta', 'Start free →'), '/v1/portal')}
|
|
49
|
+
${tier('own-node', tr('pricing.ownName', 'Own node'), '49 €/' + tr('pricing.mo', 'mo'), [
|
|
50
|
+
tr('pricing.own1', 'Your own instance, your data, BYOK.'),
|
|
51
|
+
tr('pricing.own2', 'No backups, community support.'),
|
|
52
|
+
], tr('pricing.contactCta', 'Contact us →'), CONTACT)}
|
|
53
|
+
${tier('own-node-pro', tr('pricing.proName', 'Own node Pro'), '99 €/' + tr('pricing.mo', 'mo'), [
|
|
54
|
+
tr('pricing.pro1', 'Backups and a bigger instance.'),
|
|
55
|
+
tr('pricing.pro2', 'Ready-made agent packages (CrewAI crew installed).'),
|
|
56
|
+
tr('pricing.pro3', 'Email support.'),
|
|
57
|
+
], tr('pricing.contactCta', 'Contact us →'), CONTACT, true)}
|
|
58
|
+
</div>
|
|
59
|
+
|
|
60
|
+
<div class="ld-tiers" id="packages">
|
|
61
|
+
${tier(null, tr('pricing.dfyName', 'Done for you'), '990 € + 49 €/' + tr('pricing.mo', 'mo') + ' · 2500 € + 250 €/' + tr('pricing.mo', 'mo'), [
|
|
62
|
+
tr('pricing.dfy1', 'Installation, your agents built, onboarding.'),
|
|
63
|
+
tr('pricing.dfy2', 'For small businesses.'),
|
|
64
|
+
tr('pricing.dfyExample', 'Example: a barbershop. Every morning at 7, your phone has the day’s bookings, yesterday’s feedback and a social post ready to approve.'),
|
|
65
|
+
], tr('pricing.dfyCta', 'See what it looks like →'), '/v1/business')}
|
|
66
|
+
${tier(null, tr('pricing.entName', 'Enterprise'), tr('pricing.entPrice', 'contact'), [
|
|
67
|
+
tr('pricing.ent1', 'Governance, data inside your own walls.'),
|
|
68
|
+
tr('pricing.ent2', 'Health and finance sectors.'),
|
|
69
|
+
], tr('pricing.contactCta', 'Contact us →'), CONTACT)}
|
|
70
|
+
</div>
|
|
71
|
+
|
|
72
|
+
<${ContactCard} />
|
|
73
|
+
</div>
|
|
74
|
+
`;
|
|
75
|
+
}
|