@pylonsync/create-pylon 0.19.1 → 0.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/create-pylon.js +30 -7
- package/package.json +1 -1
- package/templates/agency/.env.example +7 -0
- package/templates/agency/README.md +28 -3
- package/templates/agency/app/(marketing)/contact-form.tsx +20 -18
- package/templates/agency/app/(marketing)/page.tsx +3 -3
- package/templates/agency/app/(marketing)/work/page.tsx +3 -3
- package/templates/agency/app/auth-form.tsx +4 -4
- package/templates/agency/app/dashboard/dashboard-client.tsx +110 -64
- package/templates/agency/app/dashboard/page.tsx +14 -16
- package/templates/agency/app/icon.svg +3 -0
- package/templates/agency/app/login/page.tsx +12 -14
- package/templates/agency/components/marketing.tsx +12 -5
- package/templates/agency/functions/seedStudioBackoffice.ts +42 -11
- package/templates/agency/lib/agency.ts +22 -0
- package/templates/agency/lib/demo.ts +118 -0
- package/templates/agency/lib/site.config.ts +19 -13
- package/templates/agency/public/favicon.ico +0 -0
- package/templates/agency/tests/agency.test.ts +57 -0
- package/templates/ai-chat/.env.example +13 -27
- package/templates/ai-chat/README.md +53 -60
- package/templates/ai-chat/app/(chat)/c/[id]/page.tsx +19 -0
- package/templates/ai-chat/app/(chat)/chat-client.tsx +657 -622
- package/templates/ai-chat/app/(chat)/layout.tsx +6 -43
- package/templates/ai-chat/app/(chat)/not-found.tsx +6 -10
- package/templates/ai-chat/app/(chat)/page.tsx +7 -11
- package/templates/ai-chat/app/auth-form.tsx +45 -38
- package/templates/ai-chat/app/error.tsx +9 -12
- package/templates/ai-chat/app/globals.css +464 -102
- package/templates/ai-chat/app/layout.tsx +11 -19
- package/templates/ai-chat/app/login/page.tsx +39 -24
- package/templates/ai-chat/app.ts +118 -43
- package/templates/ai-chat/components/chat/composer.tsx +193 -0
- package/templates/ai-chat/components/chat/empty-state.tsx +65 -0
- package/templates/ai-chat/components/chat/icons.tsx +198 -0
- package/templates/ai-chat/components/chat/markdown.tsx +164 -0
- package/templates/ai-chat/components/chat/message.tsx +183 -0
- package/templates/ai-chat/components/chat/setup.tsx +71 -0
- package/templates/ai-chat/components/chat/sidebar.tsx +288 -0
- package/templates/ai-chat/functions/_beginTurn.ts +145 -0
- package/templates/ai-chat/functions/_finishTurn.ts +55 -0
- package/templates/ai-chat/functions/assistantStatus.ts +13 -0
- package/templates/ai-chat/functions/claimGuestHistory.ts +39 -0
- package/templates/ai-chat/functions/createConversation.ts +28 -0
- package/templates/ai-chat/functions/deleteConversation.ts +15 -20
- package/templates/ai-chat/functions/prepareGuestClaim.ts +32 -0
- package/templates/ai-chat/functions/renameConversation.ts +19 -0
- package/templates/ai-chat/functions/respond.ts +88 -0
- package/templates/ai-chat/functions/seedExamples.ts +46 -0
- package/templates/ai-chat/functions/stopResponse.ts +30 -0
- package/templates/ai-chat/lib/brand-icon.ts +7 -0
- package/templates/ai-chat/lib/chat.ts +277 -0
- package/templates/ai-chat/lib/claim.ts +15 -0
- package/templates/ai-chat/lib/examples.ts +179 -0
- package/templates/ai-chat/lib/highlight.ts +107 -0
- package/templates/ai-chat/lib/markdown.ts +317 -0
- package/templates/ai-chat/lib/provider.ts +38 -0
- package/templates/ai-chat/lib/site.config.ts +55 -39
- package/templates/ai-chat/tests/chat.test.ts +174 -0
- package/templates/ai-chat/tests/components.test.tsx +57 -0
- package/templates/ai-chat/tests/markdown.test.ts +88 -0
- package/templates/ai-chat/tests/provider.test.ts +32 -0
- package/templates/backend/consumer/apps/api/app/layout.tsx +33 -0
- package/templates/backend/consumer/apps/api/app/page.tsx +54 -0
- package/templates/backend/consumer/apps/api/app.ts +166 -0
- package/templates/backend/consumer/apps/api/functions/addComment.ts +24 -0
- package/templates/backend/consumer/apps/api/functions/createPost.ts +19 -36
- package/templates/backend/consumer/apps/api/functions/deletePost.ts +15 -13
- package/templates/backend/consumer/apps/api/functions/myProfile.ts +5 -8
- package/templates/backend/consumer/apps/api/functions/seedDemo.ts +95 -0
- package/templates/backend/consumer/apps/api/functions/setFollow.ts +36 -0
- package/templates/backend/consumer/apps/api/functions/setLike.ts +36 -0
- package/templates/backend/consumer/apps/api/functions/upsertProfile.ts +43 -37
- package/templates/backend/consumer/apps/api/lib/seed.ts +89 -0
- package/templates/backend/consumer/apps/api/lib/social.ts +50 -0
- package/templates/backend/consumer/apps/api/package.json +8 -2
- package/templates/backend/consumer/apps/api/public/images/posts/arch.jpg +0 -0
- package/templates/backend/consumer/apps/api/public/images/posts/coffee.jpg +0 -0
- package/templates/backend/consumer/apps/api/public/images/posts/dog.jpg +0 -0
- package/templates/backend/consumer/apps/api/public/images/posts/lisbon.jpg +0 -0
- package/templates/backend/consumer/apps/api/public/images/posts/ramen.jpg +0 -0
- package/templates/backend/consumer/apps/api/public/images/posts/run.jpg +0 -0
- package/templates/backend/consumer/apps/api/public/images/posts/sunset.jpg +0 -0
- package/templates/backend/consumer/apps/api/tsconfig.json +12 -2
- package/templates/chat/README.md +43 -21
- package/templates/chat/app/chat-client.tsx +495 -85
- package/templates/chat/app/error.tsx +1 -1
- package/templates/chat/app/globals.css +170 -54
- package/templates/chat/app/layout.tsx +12 -9
- package/templates/chat/app/not-found.tsx +4 -2
- package/templates/chat/app/page.tsx +9 -19
- package/templates/chat/app.ts +96 -17
- package/templates/chat/components/chat/avatar.tsx +45 -0
- package/templates/chat/components/chat/composer.tsx +110 -0
- package/templates/chat/components/chat/message-list.tsx +285 -0
- package/templates/chat/components/chat/sidebar.tsx +190 -0
- package/templates/chat/functions/joinChat.ts +83 -0
- package/templates/chat/functions/renameMember.ts +25 -0
- package/templates/chat/lib/chat.ts +188 -0
- package/templates/chat/lib/seed.ts +78 -0
- package/templates/chat/tests/chat.test.ts +159 -0
- package/templates/chat/tests/composer.test.tsx +54 -0
- package/templates/consumer/README.md +3 -3
- package/templates/consumer/app/globals.css +10 -3
- package/templates/consumer/app/icon.svg +1 -0
- package/templates/consumer/components/social/avatar.tsx +25 -11
- package/templates/consumer/functions/seedFeed.ts +3 -3
- package/templates/consumer/lib/seed.ts +43 -85
- package/templates/consumer/public/favicon.ico +0 -0
- package/templates/consumer/public/images/posts/arch.jpg +0 -0
- package/templates/consumer/public/images/posts/coffee.jpg +0 -0
- package/templates/consumer/public/images/posts/dog.jpg +0 -0
- package/templates/consumer/public/images/posts/lisbon.jpg +0 -0
- package/templates/consumer/public/images/posts/ramen.jpg +0 -0
- package/templates/consumer/public/images/posts/run.jpg +0 -0
- package/templates/consumer/public/images/posts/sunset.jpg +0 -0
- package/templates/consumer/tests/seed.test.ts +39 -0
- package/templates/creator/.env.example +7 -0
- package/templates/creator/README.md +24 -5
- package/templates/creator/app/(marketing)/newsletter-signup.tsx +33 -12
- package/templates/creator/app/auth-form.tsx +7 -7
- package/templates/creator/app/dashboard/dashboard-client.tsx +137 -100
- package/templates/creator/app/dashboard/page.tsx +18 -18
- package/templates/creator/app/icon.svg +2 -0
- package/templates/creator/app/login/page.tsx +14 -14
- package/templates/creator/app.ts +4 -4
- package/templates/creator/functions/seedDemo.ts +32 -0
- package/templates/creator/functions/subscribe.ts +4 -15
- package/templates/creator/lib/demo.ts +94 -0
- package/templates/creator/lib/site.config.ts +15 -10
- package/templates/creator/lib/stats.ts +35 -0
- package/templates/creator/public/favicon.ico +0 -0
- package/templates/creator/tests/counter.test.ts +92 -0
- package/templates/creator/tests/live-count.test.tsx +45 -0
- package/templates/crm/README.md +22 -1
- package/templates/crm/app/apple-icon.png +0 -0
- package/templates/crm/app/companies/companies-view.tsx +32 -14
- package/templates/crm/app/companies/page.tsx +2 -1
- package/templates/crm/app/contacts/contacts-view.tsx +34 -10
- package/templates/crm/app/contacts/page.tsx +2 -1
- package/templates/crm/app/deals/[id]/deal-view.tsx +137 -98
- package/templates/crm/app/deals/[id]/page.tsx +2 -1
- package/templates/crm/app/globals.css +29 -14
- package/templates/crm/app/icon.svg +1 -0
- package/templates/crm/app/layout.tsx +7 -5
- package/templates/crm/app/page.tsx +2 -1
- package/templates/crm/app/pipeline-view.tsx +5 -5
- package/templates/crm/app/workspace.tsx +29 -17
- package/templates/crm/app.ts +5 -5
- package/templates/crm/components/activity-timeline.tsx +14 -4
- package/templates/crm/components/app-shell.tsx +94 -0
- package/templates/crm/components/auth-form.tsx +35 -5
- package/templates/crm/components/avatar.tsx +5 -1
- package/templates/crm/components/board-skeleton.tsx +5 -5
- package/templates/crm/components/brand-mark.tsx +25 -0
- package/templates/crm/components/command-palette.tsx +17 -17
- package/templates/crm/components/data-table.tsx +97 -36
- package/templates/crm/components/deal-card.tsx +23 -27
- package/templates/crm/components/filter-tabs.tsx +101 -0
- package/templates/crm/components/metrics-bar.tsx +6 -6
- package/templates/crm/components/nav.tsx +15 -0
- package/templates/crm/components/page-header.tsx +43 -7
- package/templates/crm/components/pipeline-board.tsx +16 -10
- package/templates/crm/components/require-auth.tsx +24 -26
- package/templates/crm/components/sidebar.tsx +54 -49
- package/templates/crm/components/stage-badge.tsx +4 -2
- package/templates/crm/components/ui/button.tsx +5 -4
- package/templates/crm/components/ui/dialog.tsx +1 -1
- package/templates/crm/components/ui/input.tsx +1 -1
- package/templates/crm/components/ui/select.tsx +1 -1
- package/templates/crm/components/ui/textarea.tsx +1 -1
- package/templates/crm/functions/seedWorkspace.ts +32 -17
- package/templates/crm/lib/brand.ts +9 -0
- package/templates/crm/lib/demo-team.ts +51 -0
- package/templates/crm/lib/pipeline.ts +37 -0
- package/templates/crm/lib/search.ts +4 -3
- package/templates/crm/lib/seed.ts +53 -8
- package/templates/crm/public/favicon.ico +0 -0
- package/templates/crm/tests/components.test.tsx +5 -5
- package/templates/crm/tests/demo-team.test.ts +18 -0
- package/templates/crm/tests/pipeline.test.ts +59 -3
- package/templates/directory/.env.example +7 -0
- package/templates/directory/README.md +22 -3
- package/templates/directory/app/(marketing)/directory-browse.tsx +30 -12
- package/templates/directory/app/(marketing)/page.tsx +6 -4
- package/templates/directory/app/auth-form.tsx +4 -4
- package/templates/directory/app/dashboard/dashboard-client.tsx +177 -90
- package/templates/directory/app/dashboard/page.tsx +16 -14
- package/templates/directory/app/icon.svg +4 -0
- package/templates/directory/app/login/page.tsx +16 -15
- package/templates/directory/functions/seedDemo.ts +43 -0
- package/templates/directory/lib/demo.ts +145 -0
- package/templates/directory/lib/directory.ts +27 -0
- package/templates/directory/lib/site.config.ts +21 -16
- package/templates/directory/public/favicon.ico +0 -0
- package/templates/directory/tests/directory.test.ts +46 -0
- package/templates/expo/consumer/apps/expo/App.tsx +360 -288
- package/templates/expo/consumer/apps/expo/app.json +9 -1
- package/templates/expo/consumer/apps/expo/package.json +2 -1
- package/templates/helpdesk/README.md +21 -0
- package/templates/helpdesk/app/apple-icon.png +0 -0
- package/templates/helpdesk/app/customers/customers-view.tsx +23 -7
- package/templates/helpdesk/app/customers/page.tsx +2 -1
- package/templates/helpdesk/app/globals.css +29 -14
- package/templates/helpdesk/app/icon.svg +1 -0
- package/templates/helpdesk/app/inbox-view.tsx +66 -47
- package/templates/helpdesk/app/layout.tsx +7 -5
- package/templates/helpdesk/app/page.tsx +4 -7
- package/templates/helpdesk/app/tickets/[id]/page.tsx +2 -1
- package/templates/helpdesk/app/tickets/[id]/ticket-view.tsx +134 -97
- package/templates/helpdesk/app/workspace.tsx +33 -22
- package/templates/helpdesk/app.ts +2 -2
- package/templates/helpdesk/components/app-shell.tsx +94 -0
- package/templates/helpdesk/components/auth-form.tsx +35 -5
- package/templates/helpdesk/components/avatar.tsx +5 -1
- package/templates/helpdesk/components/brand-mark.tsx +28 -0
- package/templates/helpdesk/components/command-palette.tsx +17 -17
- package/templates/helpdesk/components/data-table.tsx +97 -36
- package/templates/helpdesk/components/filter-tabs.tsx +101 -0
- package/templates/helpdesk/components/nav.tsx +14 -0
- package/templates/helpdesk/components/page-header.tsx +43 -7
- package/templates/helpdesk/components/priority-badge.tsx +49 -6
- package/templates/helpdesk/components/require-auth.tsx +24 -26
- package/templates/helpdesk/components/sidebar.tsx +54 -48
- package/templates/helpdesk/components/sla-indicator.tsx +5 -8
- package/templates/helpdesk/components/thread.tsx +7 -7
- package/templates/helpdesk/components/ticket-list.tsx +79 -19
- package/templates/helpdesk/components/ui/button.tsx +5 -4
- package/templates/helpdesk/components/ui/dialog.tsx +1 -1
- package/templates/helpdesk/components/ui/input.tsx +1 -1
- package/templates/helpdesk/components/ui/select.tsx +1 -1
- package/templates/helpdesk/components/ui/textarea.tsx +1 -1
- package/templates/helpdesk/functions/seedWorkspace.ts +25 -15
- package/templates/helpdesk/lib/brand.ts +9 -0
- package/templates/helpdesk/lib/demo-team.ts +51 -0
- package/templates/helpdesk/lib/search.ts +4 -3
- package/templates/helpdesk/lib/seed.ts +43 -2
- package/templates/helpdesk/lib/tickets.ts +68 -20
- package/templates/helpdesk/public/favicon.ico +0 -0
- package/templates/helpdesk/tests/components.test.tsx +10 -0
- package/templates/helpdesk/tests/demo-team.test.ts +18 -0
- package/templates/helpdesk/tests/tickets.test.ts +70 -0
- package/templates/imessage/.env.example +49 -0
- package/templates/imessage/AGENTS.md +153 -0
- package/templates/imessage/CLAUDE.md +7 -0
- package/templates/imessage/README.md +187 -0
- package/templates/imessage/app/console.tsx +382 -0
- package/templates/imessage/app/contacts/page.tsx +13 -0
- package/templates/imessage/app/error.tsx +16 -0
- package/templates/imessage/app/gate.tsx +31 -0
- package/templates/imessage/app/globals.css +183 -0
- package/templates/imessage/app/layout.tsx +17 -0
- package/templates/imessage/app/login/login-form.tsx +115 -0
- package/templates/imessage/app/login/page.tsx +30 -0
- package/templates/imessage/app/not-found.tsx +13 -0
- package/templates/imessage/app/page.tsx +14 -0
- package/templates/imessage/app/robots.ts +6 -0
- package/templates/imessage/app/setup/page.tsx +13 -0
- package/templates/imessage/app/sign-out.tsx +20 -0
- package/templates/imessage/app.ts +299 -0
- package/templates/imessage/bunfig +4 -0
- package/templates/imessage/components/avatar.tsx +46 -0
- package/templates/imessage/components/contact-panel.tsx +263 -0
- package/templates/imessage/components/contacts-view.tsx +172 -0
- package/templates/imessage/components/conversation-list.tsx +129 -0
- package/templates/imessage/components/nav-rail.tsx +67 -0
- package/templates/imessage/components/setup-view.tsx +408 -0
- package/templates/imessage/components/thread.tsx +229 -0
- package/templates/imessage/components/ui/badge.tsx +37 -0
- package/templates/imessage/components/ui/button.tsx +56 -0
- package/templates/imessage/components/ui/card.tsx +90 -0
- package/templates/imessage/components/ui/dialog.tsx +110 -0
- package/templates/imessage/components/ui/input.tsx +30 -0
- package/templates/imessage/components/ui/label.tsx +21 -0
- package/templates/imessage/components/ui/select.tsx +39 -0
- package/templates/imessage/components/ui/table.tsx +124 -0
- package/templates/imessage/components/ui/textarea.tsx +24 -0
- package/templates/imessage/components.json +20 -0
- package/templates/imessage/functions/answerNow.ts +34 -0
- package/templates/imessage/functions/assistant.ts +96 -0
- package/templates/imessage/functions/beginTurn.ts +101 -0
- package/templates/imessage/functions/cancelReminder.ts +13 -0
- package/templates/imessage/functions/claimOutbound.ts +44 -0
- package/templates/imessage/functions/clearDemo.ts +18 -0
- package/templates/imessage/functions/deleteNote.ts +11 -0
- package/templates/imessage/functions/deliverOutbound.ts +55 -0
- package/templates/imessage/functions/finishTurn.ts +84 -0
- package/templates/imessage/functions/fireReminder.ts +42 -0
- package/templates/imessage/functions/ingestInbound.ts +22 -0
- package/templates/imessage/functions/ownerAccount.ts +13 -0
- package/templates/imessage/functions/processTurn.ts +74 -0
- package/templates/imessage/functions/relayApply.ts +102 -0
- package/templates/imessage/functions/relaySync.ts +63 -0
- package/templates/imessage/functions/releaseOutbound.ts +18 -0
- package/templates/imessage/functions/resolveTurn.ts +25 -0
- package/templates/imessage/functions/saveContact.ts +27 -0
- package/templates/imessage/functions/seedDemo.ts +70 -0
- package/templates/imessage/functions/sendOwnerMessage.ts +28 -0
- package/templates/imessage/functions/sendblueWebhook.ts +49 -0
- package/templates/imessage/functions/setContactStatus.ts +17 -0
- package/templates/imessage/functions/settleOutbound.ts +35 -0
- package/templates/imessage/functions/setupStatus.ts +33 -0
- package/templates/imessage/functions/sweep.ts +58 -0
- package/templates/imessage/functions/toolCancelReminder.ts +17 -0
- package/templates/imessage/functions/toolForgetNote.ts +15 -0
- package/templates/imessage/functions/toolListNotes.ts +16 -0
- package/templates/imessage/functions/toolListReminders.ts +17 -0
- package/templates/imessage/functions/toolSaveNote.ts +22 -0
- package/templates/imessage/functions/toolSetReminder.ts +55 -0
- package/templates/imessage/functions/updateSettings.ts +48 -0
- package/templates/imessage/gitignore +11 -0
- package/templates/imessage/lib/agent-call.ts +251 -0
- package/templates/imessage/lib/assistant.ts +74 -0
- package/templates/imessage/lib/chunk.ts +135 -0
- package/templates/imessage/lib/demo.ts +68 -0
- package/templates/imessage/lib/format.ts +170 -0
- package/templates/imessage/lib/gate.ts +104 -0
- package/templates/imessage/lib/handles.ts +67 -0
- package/templates/imessage/lib/provider.ts +38 -0
- package/templates/imessage/lib/relay-protocol.ts +95 -0
- package/templates/imessage/lib/reminders.ts +41 -0
- package/templates/imessage/lib/secrets.ts +59 -0
- package/templates/imessage/lib/sendblue.ts +173 -0
- package/templates/imessage/lib/store.ts +407 -0
- package/templates/imessage/lib/transport.ts +135 -0
- package/templates/imessage/lib/utils.ts +10 -0
- package/templates/imessage/package.json +40 -0
- package/templates/imessage/relay/.env.example +14 -0
- package/templates/imessage/relay/applescript.ts +70 -0
- package/templates/imessage/relay/chatdb.ts +165 -0
- package/templates/imessage/relay/client.ts +41 -0
- package/templates/imessage/relay/main.ts +137 -0
- package/templates/imessage/relay/state.ts +35 -0
- package/templates/imessage/tests/agent-call.test.ts +112 -0
- package/templates/imessage/tests/applescript.test.ts +88 -0
- package/templates/imessage/tests/chatdb.test.ts +139 -0
- package/templates/imessage/tests/chunk.test.ts +61 -0
- package/templates/imessage/tests/fake-ctx.ts +88 -0
- package/templates/imessage/tests/ingest.test.ts +141 -0
- package/templates/imessage/tests/lib.test.ts +99 -0
- package/templates/imessage/tests/review-fixes.test.ts +138 -0
- package/templates/imessage/tests/secrets.test.ts +36 -0
- package/templates/imessage/tests/sendblue.test.ts +143 -0
- package/templates/imessage/tests/setup.ts +5 -0
- package/templates/imessage/tsconfig.json +18 -0
- package/templates/inventory/README.md +21 -0
- package/templates/inventory/app/apple-icon.png +0 -0
- package/templates/inventory/app/globals.css +29 -14
- package/templates/inventory/app/icon.svg +1 -0
- package/templates/inventory/app/layout.tsx +7 -5
- package/templates/inventory/app/movements/movements-view.tsx +32 -10
- package/templates/inventory/app/movements/page.tsx +2 -1
- package/templates/inventory/app/page.tsx +4 -6
- package/templates/inventory/app/products/[id]/page.tsx +2 -1
- package/templates/inventory/app/products/[id]/product-view.tsx +55 -25
- package/templates/inventory/app/products-view.tsx +74 -33
- package/templates/inventory/app/workspace.tsx +34 -16
- package/templates/inventory/app.ts +10 -2
- package/templates/inventory/components/app-shell.tsx +94 -0
- package/templates/inventory/components/auth-form.tsx +35 -5
- package/templates/inventory/components/avatar.tsx +5 -1
- package/templates/inventory/components/brand-mark.tsx +25 -0
- package/templates/inventory/components/category-tile.tsx +49 -0
- package/templates/inventory/components/command-palette.tsx +17 -17
- package/templates/inventory/components/data-table.tsx +97 -36
- package/templates/inventory/components/filter-tabs.tsx +101 -0
- package/templates/inventory/components/nav.tsx +14 -0
- package/templates/inventory/components/page-header.tsx +43 -7
- package/templates/inventory/components/require-auth.tsx +24 -26
- package/templates/inventory/components/sidebar.tsx +54 -48
- package/templates/inventory/components/stock-level.tsx +30 -14
- package/templates/inventory/components/summary-bar.tsx +8 -15
- package/templates/inventory/components/ui/button.tsx +5 -4
- package/templates/inventory/components/ui/dialog.tsx +1 -1
- package/templates/inventory/components/ui/input.tsx +1 -1
- package/templates/inventory/components/ui/select.tsx +1 -1
- package/templates/inventory/components/ui/textarea.tsx +1 -1
- package/templates/inventory/functions/seedWorkspace.ts +18 -8
- package/templates/inventory/lib/brand.ts +9 -0
- package/templates/inventory/lib/demo-team.ts +51 -0
- package/templates/inventory/lib/search.ts +4 -3
- package/templates/inventory/lib/seed.ts +29 -2
- package/templates/inventory/lib/stock.ts +32 -0
- package/templates/inventory/public/favicon.ico +0 -0
- package/templates/inventory/tests/demo-team.test.ts +18 -0
- package/templates/inventory/tests/stock.test.ts +29 -0
- package/templates/invoices/README.md +16 -0
- package/templates/invoices/app/apple-icon.png +0 -0
- package/templates/invoices/app/clients/clients-view.tsx +18 -6
- package/templates/invoices/app/clients/page.tsx +2 -1
- package/templates/invoices/app/globals.css +29 -14
- package/templates/invoices/app/icon.svg +1 -0
- package/templates/invoices/app/invoices/[id]/invoice-view.tsx +72 -48
- package/templates/invoices/app/invoices/[id]/page.tsx +2 -1
- package/templates/invoices/app/invoices-view.tsx +81 -32
- package/templates/invoices/app/layout.tsx +7 -5
- package/templates/invoices/app/page.tsx +4 -6
- package/templates/invoices/app/workspace.tsx +26 -16
- package/templates/invoices/app.ts +2 -2
- package/templates/invoices/components/app-shell.tsx +94 -0
- package/templates/invoices/components/auth-form.tsx +35 -5
- package/templates/invoices/components/avatar.tsx +5 -1
- package/templates/invoices/components/brand-mark.tsx +26 -0
- package/templates/invoices/components/command-palette.tsx +17 -17
- package/templates/invoices/components/data-table.tsx +97 -36
- package/templates/invoices/components/filter-tabs.tsx +101 -0
- package/templates/invoices/components/line-items.tsx +19 -14
- package/templates/invoices/components/nav.tsx +14 -0
- package/templates/invoices/components/page-header.tsx +43 -7
- package/templates/invoices/components/require-auth.tsx +24 -26
- package/templates/invoices/components/sidebar.tsx +54 -48
- package/templates/invoices/components/status-badge.tsx +7 -7
- package/templates/invoices/components/summary-bar.tsx +5 -5
- package/templates/invoices/components/ui/button.tsx +5 -4
- package/templates/invoices/components/ui/dialog.tsx +1 -1
- package/templates/invoices/components/ui/input.tsx +1 -1
- package/templates/invoices/components/ui/select.tsx +1 -1
- package/templates/invoices/components/ui/textarea.tsx +1 -1
- package/templates/invoices/functions/recordPayment.ts +3 -0
- package/templates/invoices/lib/billing.ts +40 -0
- package/templates/invoices/lib/brand.ts +9 -0
- package/templates/invoices/lib/search.ts +4 -3
- package/templates/invoices/public/favicon.ico +0 -0
- package/templates/invoices/tests/billing.test.ts +27 -0
- package/templates/ios/consumer/apps/ios/Package.swift +4 -3
- package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/ActivityView.swift +107 -0
- package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/AppModel.swift +171 -0
- package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/AuthView.swift +182 -0
- package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/CommentsView.swift +136 -0
- package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/ComposeView.swift +152 -0
- package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/ExploreView.swift +117 -0
- package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/FeedView.swift +175 -159
- package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/MainTabView.swift +54 -0
- package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/Media.swift +271 -0
- package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/Models.swift +101 -4
- package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/PostCard.swift +245 -0
- package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/ProfileEditorView.swift +208 -0
- package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/ProfileView.swift +183 -0
- package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/RootView.swift +25 -31
- package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/SocialStore.swift +285 -0
- package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/StoryViewer.swift +155 -0
- package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/Theme.swift +82 -0
- package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/__APP_NAME_PASCAL__App.swift +4 -47
- package/templates/ios/consumer/apps/ios/project.yml +13 -9
- package/templates/local-service/.env.example +7 -0
- package/templates/local-service/README.md +26 -2
- package/templates/local-service/app/(marketing)/booking-widget.tsx +48 -9
- package/templates/local-service/app/(marketing)/page.tsx +66 -42
- package/templates/local-service/app/auth-form.tsx +8 -8
- package/templates/local-service/app/dashboard/dashboard-client.tsx +119 -92
- package/templates/local-service/app/dashboard/page.tsx +14 -14
- package/templates/local-service/app/icon.svg +2 -0
- package/templates/local-service/app/login/page.tsx +15 -15
- package/templates/local-service/functions/seedDemo.ts +49 -0
- package/templates/local-service/lib/demo.ts +135 -0
- package/templates/local-service/lib/hours.ts +44 -0
- package/templates/local-service/lib/site.config.ts +8 -3
- package/templates/local-service/lib/slots.ts +3 -0
- package/templates/local-service/public/favicon.ico +0 -0
- package/templates/local-service/tests/booking.test.ts +104 -0
- package/templates/marketplace/README.md +23 -15
- package/templates/marketplace/app/globals.css +172 -114
- package/templates/marketplace/app/layout.tsx +75 -30
- package/templates/marketplace/app/listing/[id]/page.tsx +210 -123
- package/templates/marketplace/app/me/page.tsx +4 -4
- package/templates/marketplace/app/page.tsx +221 -298
- package/templates/marketplace/app/sell/page.tsx +8 -11
- package/templates/marketplace/app.ts +32 -7
- package/templates/marketplace/client/ActivityFeed.tsx +311 -0
- package/templates/marketplace/client/AuthNav.tsx +24 -16
- package/templates/marketplace/client/ListingCard.tsx +118 -0
- package/templates/marketplace/client/LiveGrid.tsx +92 -0
- package/templates/marketplace/client/LiveListingStatus.tsx +32 -24
- package/templates/marketplace/client/LoginCard.tsx +114 -136
- package/templates/marketplace/client/MarketProvider.tsx +3 -5
- package/templates/marketplace/client/MyMarket.tsx +371 -185
- package/templates/marketplace/client/OfferPanel.tsx +421 -287
- package/templates/marketplace/client/SellForm.tsx +271 -192
- package/templates/marketplace/client/ThemeToggle.tsx +1 -0
- package/templates/marketplace/client/WatchButton.tsx +16 -16
- package/templates/marketplace/client/market.ts +61 -14
- package/templates/marketplace/functions/seedMarket.ts +143 -36
- package/templates/marketplace/functions/seedOffers.ts +101 -0
- package/templates/marketplace/lib/catalog.ts +72 -1
- package/templates/marketplace/public/images/listings/ceramic-lamp-640.webp +0 -0
- package/templates/marketplace/public/images/listings/ceramic-lamp.webp +0 -0
- package/templates/marketplace/public/images/listings/duffel-640.webp +0 -0
- package/templates/marketplace/public/images/listings/duffel.webp +0 -0
- package/templates/marketplace/public/images/listings/film-camera-640.webp +0 -0
- package/templates/marketplace/public/images/listings/film-camera.webp +0 -0
- package/templates/marketplace/public/images/listings/lounge-chair-640.webp +0 -0
- package/templates/marketplace/public/images/listings/lounge-chair.webp +0 -0
- package/templates/marketplace/public/images/listings/road-bike-640.webp +0 -0
- package/templates/marketplace/public/images/listings/road-bike.webp +0 -0
- package/templates/marketplace/public/images/listings/slipper-chair-640.webp +0 -0
- package/templates/marketplace/public/images/listings/slipper-chair.webp +0 -0
- package/templates/marketplace/public/images/listings/turntable-640.webp +0 -0
- package/templates/marketplace/public/images/listings/turntable.webp +0 -0
- package/templates/marketplace/public/images/listings/wardrobe-640.webp +0 -0
- package/templates/marketplace/public/images/listings/wardrobe.webp +0 -0
- package/templates/marketplace/public/images/listings/watch-640.webp +0 -0
- package/templates/marketplace/public/images/listings/watch.webp +0 -0
- package/templates/marketplace/public/images/listings/weekender-bag-640.webp +0 -0
- package/templates/marketplace/public/images/listings/weekender-bag.webp +0 -0
- package/templates/marketplace/tests/example.test.ts +64 -2
- package/templates/marketplace/tests/listing-card.test.tsx +61 -0
- package/templates/projects/README.md +24 -3
- package/templates/projects/app/apple-icon.png +0 -0
- package/templates/projects/app/clients/clients-view.tsx +20 -6
- package/templates/projects/app/clients/page.tsx +2 -1
- package/templates/projects/app/globals.css +29 -14
- package/templates/projects/app/icon.svg +1 -0
- package/templates/projects/app/layout.tsx +7 -5
- package/templates/projects/app/page.tsx +3 -5
- package/templates/projects/app/projects/[id]/page.tsx +2 -1
- package/templates/projects/app/projects/[id]/project-view.tsx +45 -30
- package/templates/projects/app/projects-view.tsx +105 -52
- package/templates/projects/app/workspace.tsx +41 -20
- package/templates/projects/app.ts +2 -2
- package/templates/projects/components/app-shell.tsx +94 -0
- package/templates/projects/components/auth-form.tsx +35 -5
- package/templates/projects/components/avatar.tsx +5 -1
- package/templates/projects/components/board-skeleton.tsx +3 -3
- package/templates/projects/components/brand-mark.tsx +25 -0
- package/templates/projects/components/command-palette.tsx +17 -17
- package/templates/projects/components/data-table.tsx +97 -36
- package/templates/projects/components/filter-tabs.tsx +101 -0
- package/templates/projects/components/meter.tsx +48 -0
- package/templates/projects/components/nav.tsx +14 -0
- package/templates/projects/components/page-header.tsx +43 -7
- package/templates/projects/components/require-auth.tsx +24 -26
- package/templates/projects/components/sidebar.tsx +54 -48
- package/templates/projects/components/summary-bar.tsx +49 -0
- package/templates/projects/components/task-board.tsx +10 -9
- package/templates/projects/components/time-dialog.tsx +29 -5
- package/templates/projects/components/ui/button.tsx +5 -4
- package/templates/projects/components/ui/dialog.tsx +1 -1
- package/templates/projects/components/ui/input.tsx +1 -1
- package/templates/projects/components/ui/select.tsx +1 -1
- package/templates/projects/components/ui/textarea.tsx +1 -1
- package/templates/projects/functions/seedWorkspace.ts +26 -12
- package/templates/projects/lib/brand.ts +9 -0
- package/templates/projects/lib/demo-team.ts +51 -0
- package/templates/projects/lib/search.ts +4 -3
- package/templates/projects/lib/seed.ts +35 -1
- package/templates/projects/lib/work.ts +63 -0
- package/templates/projects/public/favicon.ico +0 -0
- package/templates/projects/tests/demo-team.test.ts +18 -0
- package/templates/projects/tests/work.test.ts +65 -0
- package/templates/restaurant/.env.example +7 -0
- package/templates/restaurant/README.md +29 -3
- package/templates/restaurant/app/(marketing)/layout.tsx +4 -2
- package/templates/restaurant/app/(marketing)/page.tsx +47 -23
- package/templates/restaurant/app/(marketing)/reservation-widget.tsx +37 -7
- package/templates/restaurant/app/auth-form.tsx +8 -8
- package/templates/restaurant/app/dashboard/dashboard-client.tsx +241 -109
- package/templates/restaurant/app/dashboard/page.tsx +14 -14
- package/templates/restaurant/app/icon.svg +4 -0
- package/templates/restaurant/app/login/page.tsx +15 -15
- package/templates/restaurant/functions/seedDemo.ts +44 -0
- package/templates/restaurant/lib/demo.ts +144 -0
- package/templates/restaurant/lib/hours.ts +45 -0
- package/templates/restaurant/lib/site.config.ts +3 -3
- package/templates/restaurant/public/favicon.ico +0 -0
- package/templates/restaurant/tests/reservations.test.ts +67 -0
- package/templates/saas/README.md +54 -35
- package/templates/saas/app/(auth)/auth-form.tsx +25 -23
- package/templates/saas/app/(auth)/forgot-password/page.tsx +1 -1
- package/templates/saas/app/(auth)/layout.tsx +27 -33
- package/templates/saas/app/(auth)/login/page.tsx +1 -1
- package/templates/saas/app/(auth)/reset-password/page.tsx +1 -1
- package/templates/saas/app/(auth)/signup/page.tsx +1 -1
- package/templates/saas/app/(marketing)/layout.tsx +127 -253
- package/templates/saas/app/(marketing)/page.tsx +133 -372
- package/templates/saas/app/(marketing)/pricing/page.tsx +21 -19
- package/templates/saas/app/(marketing)/products/[slug]/page.tsx +40 -95
- package/templates/saas/app/dashboard/board-client.tsx +416 -0
- package/templates/saas/app/dashboard/dashboard-client.tsx +30 -615
- package/templates/saas/app/dashboard/layout.tsx +27 -13
- package/templates/saas/app/dashboard/overview-client.tsx +332 -0
- package/templates/saas/app/dashboard/page.tsx +29 -35
- package/templates/saas/app/dashboard/projects/[id]/page.tsx +29 -0
- package/templates/saas/app/dashboard/projects/page.tsx +11 -7
- package/templates/saas/app/dashboard/projects-client.tsx +398 -0
- package/templates/saas/app/globals.css +5 -4
- package/templates/saas/app/icon.svg +1 -0
- package/templates/saas/app/onboarding/onboarding-client.tsx +93 -26
- package/templates/saas/app/onboarding/page.tsx +6 -2
- package/templates/saas/app.ts +68 -13
- package/templates/saas/components/dashboard-shell.tsx +212 -132
- package/templates/saas/components/marketing.tsx +137 -274
- package/templates/saas/components/pricing-table.tsx +12 -11
- package/templates/saas/components/setup-checklist.tsx +31 -29
- package/templates/saas/components/task-ui.tsx +199 -0
- package/templates/saas/functions/createProject.ts +7 -13
- package/templates/saas/functions/createTask.ts +67 -0
- package/templates/saas/functions/deleteProject.ts +17 -0
- package/templates/saas/functions/deleteTask.ts +14 -0
- package/templates/saas/functions/seedWorkspace.ts +46 -0
- package/templates/saas/functions/setProjectStatus.ts +41 -0
- package/templates/saas/functions/updateProfile.ts +13 -0
- package/templates/saas/functions/updateProject.ts +4 -5
- package/templates/saas/functions/updateTask.ts +88 -0
- package/templates/saas/lib/members.ts +53 -0
- package/templates/saas/lib/names.ts +53 -0
- package/templates/saas/lib/plans.ts +11 -0
- package/templates/saas/lib/sample-workspace.ts +117 -0
- package/templates/saas/lib/site.config.ts +200 -463
- package/templates/saas/lib/tasks.ts +101 -0
- package/templates/saas/lib/use-live.ts +25 -0
- package/templates/saas/public/favicon.ico +0 -0
- package/templates/saas/public/screenshots/board.png +0 -0
- package/templates/saas/public/screenshots/members.png +0 -0
- package/templates/saas/public/screenshots/overview.png +0 -0
- package/templates/saas/public/screenshots/projects.png +0 -0
- package/templates/saas/scripts/capture-screenshots.mjs +156 -0
- package/templates/saas/tests/names.test.ts +35 -0
- package/templates/saas/tests/plans.test.ts +12 -0
- package/templates/saas/tests/sample-workspace.test.ts +46 -0
- package/templates/saas/tests/tasks.test.ts +81 -0
- package/templates/shop/README.md +25 -13
- package/templates/shop/app/(marketing)/layout.tsx +94 -67
- package/templates/shop/app/(marketing)/not-found.tsx +13 -12
- package/templates/shop/app/(marketing)/page.tsx +120 -59
- package/templates/shop/app/(marketing)/shop-client.tsx +364 -237
- package/templates/shop/app/(marketing)/success/page.tsx +75 -22
- package/templates/shop/app/globals.css +94 -1
- package/templates/shop/app/layout.tsx +4 -4
- package/templates/shop/app/login/page.tsx +4 -4
- package/templates/shop/app.ts +18 -6
- package/templates/shop/components/cart-button.tsx +33 -0
- package/templates/shop/components/marketing.tsx +17 -87
- package/templates/shop/components/shelf-count.tsx +36 -0
- package/templates/shop/functions/checkout.ts +17 -1
- package/templates/shop/lib/cart-store.ts +57 -0
- package/templates/shop/lib/shelf-store.ts +35 -0
- package/templates/shop/lib/shop.ts +27 -0
- package/templates/shop/lib/site.config.ts +108 -98
- package/templates/shop/public/images/hero.jpg +0 -0
- package/templates/shop/public/images/products/cedar-smoke.jpg +0 -0
- package/templates/shop/public/images/products/fig-leaf.jpg +0 -0
- package/templates/shop/public/images/products/linen.jpg +0 -0
- package/templates/shop/public/images/products/sea-salt.jpg +0 -0
- package/templates/shop/public/images/products/tobacco-oak.jpg +0 -0
- package/templates/shop/tests/shop.test.ts +64 -0
- package/templates/waitlist/.env.example +7 -0
- package/templates/waitlist/README.md +29 -13
- package/templates/waitlist/app/(marketing)/page.tsx +9 -7
- package/templates/waitlist/app/(marketing)/waitlist-hero.tsx +64 -31
- package/templates/waitlist/app/auth-form.tsx +8 -8
- package/templates/waitlist/app/dashboard/dashboard-client.tsx +132 -95
- package/templates/waitlist/app/dashboard/page.tsx +17 -16
- package/templates/waitlist/app/icon.svg +2 -0
- package/templates/waitlist/app/login/page.tsx +14 -14
- package/templates/waitlist/app.ts +6 -6
- package/templates/waitlist/functions/joinWaitlist.ts +6 -18
- package/templates/waitlist/functions/seedDemo.ts +32 -0
- package/templates/waitlist/lib/demo.ts +94 -0
- package/templates/waitlist/lib/launch.ts +19 -0
- package/templates/waitlist/lib/site.config.ts +14 -9
- package/templates/waitlist/lib/stats.ts +35 -0
- package/templates/waitlist/public/favicon.ico +0 -0
- package/templates/waitlist/tests/counter.test.ts +107 -0
- package/templates/waitlist/tests/live-count.test.tsx +48 -0
- package/templates/ai-chat/tests/example.test.ts +0 -12
- package/templates/backend/consumer/apps/api/functions/feed.ts +0 -57
- package/templates/backend/consumer/apps/api/functions/profilePosts.ts +0 -17
- package/templates/backend/consumer/apps/api/functions/toggleLike.ts +0 -48
- package/templates/backend/consumer/apps/api/schema.ts +0 -143
- package/templates/chat/functions/_keep.ts +0 -13
- package/templates/chat/tests/example.test.ts +0 -12
- package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/ProfileSetupView.swift +0 -68
- package/templates/marketplace/client/LiveTicker.tsx +0 -116
- package/templates/marketplace/client/ScrollToListingsLink.tsx +0 -29
- package/templates/marketplace/public/images/marketplace-hero.webp +0 -0
|
@@ -2,17 +2,25 @@ import { mutation } from "@pylonsync/functions";
|
|
|
2
2
|
import { emailMatchesOwner } from "../lib/owner";
|
|
3
3
|
import { siteConfig } from "../lib/site.config";
|
|
4
4
|
import { lineItemsTotal, slugify, type ProjectRow } from "../lib/agency";
|
|
5
|
+
import { DEMO_INQUIRIES, daysAgoDate, demoDataEnabled } from "../lib/demo";
|
|
5
6
|
|
|
6
|
-
// seedStudioBackoffice — owner-only, idempotent. Fills the
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
7
|
+
// seedStudioBackoffice — owner-only, development-only, idempotent. Fills the
|
|
8
|
+
// dashboard's pipeline, CRM, and billing with demo leads (lib/demo.ts) and the
|
|
9
|
+
// demo clients/invoices from config, so the tabs are not empty on first
|
|
10
|
+
// sign-in.
|
|
11
|
+
//
|
|
12
|
+
// It writes nothing unless demo data is on (lib/demo.ts: PYLON_DEMO_DATA, else
|
|
13
|
+
// a `pylon dev` process), so `pylon start`, Docker, and Pylon Cloud deploys do
|
|
14
|
+
// not get demo rows. Each table is seeded only while it is empty, so deleting
|
|
15
|
+
// every lead in development seeds them again on the next load. Private data
|
|
16
|
+
// (PII + money) is only ever seeded for the signed-in owner, never by an
|
|
17
|
+
// anonymous visitor, which is why this is owner-gated, unlike the public
|
|
18
|
+
// seedProjects.
|
|
11
19
|
//
|
|
12
20
|
// Invoices link to clients by name and to projects by slug; we resolve those to
|
|
13
|
-
// ids here. Projects may already be seeded (the public site calls
|
|
14
|
-
// if a project isn't present yet, the invoice still carries its
|
|
15
|
-
// doesn't deep-link.
|
|
21
|
+
// ids here. Projects may already be seeded (the public site calls
|
|
22
|
+
// seedProjects); if a project isn't present yet, the invoice still carries its
|
|
23
|
+
// title and just doesn't deep-link.
|
|
16
24
|
export default mutation<Record<string, never>, { seeded: boolean }>({
|
|
17
25
|
auth: "user",
|
|
18
26
|
async handler(ctx) {
|
|
@@ -21,9 +29,29 @@ export default mutation<Record<string, never>, { seeded: boolean }>({
|
|
|
21
29
|
throw ctx.error("POLICY_DENIED", "Only the owner can seed the back-office.");
|
|
22
30
|
}
|
|
23
31
|
|
|
32
|
+
if (!demoDataEnabled(ctx.env)) return { seeded: false };
|
|
33
|
+
|
|
24
34
|
await ctx.db.advisoryLock("agency_seed_backoffice");
|
|
35
|
+
const nowMs = Date.now();
|
|
36
|
+
|
|
37
|
+
const existingInquiries = await ctx.db.unsafe.list("Inquiry");
|
|
38
|
+
if (existingInquiries.length === 0) {
|
|
39
|
+
for (const q of DEMO_INQUIRIES) {
|
|
40
|
+
await ctx.db.unsafe.insert("Inquiry", {
|
|
41
|
+
name: q.name,
|
|
42
|
+
email: q.email,
|
|
43
|
+
company: q.company,
|
|
44
|
+
projectType: q.projectType,
|
|
45
|
+
budget: q.budget,
|
|
46
|
+
message: q.message,
|
|
47
|
+
status: q.status,
|
|
48
|
+
createdAt: new Date(nowMs - q.hoursAgo * 3_600_000).toISOString(),
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
25
53
|
const existingClients = await ctx.db.unsafe.list("Client");
|
|
26
|
-
if (existingClients.length > 0) return { seeded:
|
|
54
|
+
if (existingClients.length > 0) return { seeded: existingInquiries.length === 0 };
|
|
27
55
|
|
|
28
56
|
const now = new Date().toISOString();
|
|
29
57
|
|
|
@@ -62,8 +90,11 @@ export default mutation<Record<string, never>, { seeded: boolean }>({
|
|
|
62
90
|
lineItems: JSON.stringify(inv.lineItems),
|
|
63
91
|
amountCents: lineItemsTotal(inv.lineItems),
|
|
64
92
|
status: inv.status ?? "draft",
|
|
65
|
-
issuedAt: inv.
|
|
66
|
-
dueAt:
|
|
93
|
+
issuedAt: inv.issuedDaysAgo !== undefined ? daysAgoDate(inv.issuedDaysAgo, nowMs) : null,
|
|
94
|
+
dueAt:
|
|
95
|
+
inv.issuedDaysAgo !== undefined
|
|
96
|
+
? daysAgoDate(inv.issuedDaysAgo - (inv.termsDays ?? 30), nowMs)
|
|
97
|
+
: null,
|
|
67
98
|
notes: null,
|
|
68
99
|
createdAt: now,
|
|
69
100
|
});
|
|
@@ -187,3 +187,25 @@ export function viewFromRow(r: ProjectRow): ProjectView {
|
|
|
187
187
|
liveUrl: r.liveUrl,
|
|
188
188
|
};
|
|
189
189
|
}
|
|
190
|
+
|
|
191
|
+
// The booking window the site names next to the open-slot count. An empty
|
|
192
|
+
// label means the quarter a new project could start in: the quarter of the
|
|
193
|
+
// date three weeks from today. It is never a past quarter, and in the last
|
|
194
|
+
// three weeks of a quarter it names the next one. The owner can set a fixed
|
|
195
|
+
// label ("January", "after the holidays") from the dashboard.
|
|
196
|
+
const LEAD_DAYS = 21;
|
|
197
|
+
export function quarterLabel(nowMs: number): string {
|
|
198
|
+
const d = new Date(nowMs);
|
|
199
|
+
return `Q${Math.floor(d.getMonth() / 3) + 1} ${d.getFullYear()}`;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
export function bookingWindow(label: string | null | undefined, nowMs: number): string {
|
|
203
|
+
const fixed = (label ?? "").trim();
|
|
204
|
+
return fixed || quarterLabel(nowMs + LEAD_DAYS * 86_400_000);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// The work grid is two columns. With an odd number of case studies the first
|
|
208
|
+
// one spans both columns, so the last row never holds a single card.
|
|
209
|
+
export function isWideCard(index: number, count: number): boolean {
|
|
210
|
+
return count > 1 && count % 2 === 1 && index === 0;
|
|
211
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
// Demo back-office data for local development.
|
|
2
|
+
//
|
|
3
|
+
// The owner's first dashboard load in `pylon dev` fills an empty studio with
|
|
4
|
+
// fictional leads, clients, and invoices (functions/seedStudioBackoffice.ts),
|
|
5
|
+
// so the pipeline, CRM, and billing tabs have rows. A deploy does not seed:
|
|
6
|
+
// `demoDataEnabled` is true only when PYLON_DEMO_DATA is on, or when it is
|
|
7
|
+
// unset and the process runs under `pylon dev`. Set PYLON_DEMO_DATA=0 to turn
|
|
8
|
+
// it off in development too.
|
|
9
|
+
//
|
|
10
|
+
// The public portfolio (functions/seedProjects.ts) is different: it copies the
|
|
11
|
+
// case studies from lib/site.config.ts, which is the studio's own content, so it
|
|
12
|
+
// runs in every environment.
|
|
13
|
+
|
|
14
|
+
const ON = new Set(["1", "true", "yes", "on"]);
|
|
15
|
+
const OFF = new Set(["0", "false", "no", "off"]);
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* True when demo rows may be written. An explicit PYLON_DEMO_DATA wins.
|
|
19
|
+
* Otherwise it needs both PYLON_DEV_MODE on and PYLON_DEV_WATCH_DIR set. Only
|
|
20
|
+
* `pylon dev` sets PYLON_DEV_WATCH_DIR; `pylon start` and the Docker image do
|
|
21
|
+
* not, and the image turns PYLON_DEV_MODE on by default, so dev mode alone is
|
|
22
|
+
* not proof of a local machine.
|
|
23
|
+
*/
|
|
24
|
+
export function demoDataEnabled(env: Record<string, string | undefined>): boolean {
|
|
25
|
+
const explicit = env.PYLON_DEMO_DATA?.trim().toLowerCase();
|
|
26
|
+
if (explicit && ON.has(explicit)) return true;
|
|
27
|
+
if (explicit && OFF.has(explicit)) return false;
|
|
28
|
+
const dev = env.PYLON_DEV_MODE?.trim().toLowerCase();
|
|
29
|
+
const devMode = dev === "1" || dev === "true";
|
|
30
|
+
return devMode && Boolean(env.PYLON_DEV_WATCH_DIR?.trim());
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Local "YYYY-MM-DD" for `daysAgo` days before `nowMs` (negative = ahead). */
|
|
34
|
+
export function daysAgoDate(daysAgo: number, nowMs: number): string {
|
|
35
|
+
const d = new Date(nowMs);
|
|
36
|
+
d.setDate(d.getDate() - daysAgo);
|
|
37
|
+
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface DemoInquiry {
|
|
41
|
+
name: string;
|
|
42
|
+
email: string;
|
|
43
|
+
company: string;
|
|
44
|
+
projectType: string;
|
|
45
|
+
budget: string;
|
|
46
|
+
message: string;
|
|
47
|
+
status: "new" | "booked" | "declined";
|
|
48
|
+
/** Hours before the seed runs. */
|
|
49
|
+
hoursAgo: number;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Fictional leads. Project types and budgets use the contact form's options.
|
|
53
|
+
export const DEMO_INQUIRIES: DemoInquiry[] = [
|
|
54
|
+
{
|
|
55
|
+
name: "Priya Raman",
|
|
56
|
+
email: "priya@fernhillvet.example",
|
|
57
|
+
company: "Fernhill Veterinary",
|
|
58
|
+
projectType: "New product (0→1)",
|
|
59
|
+
budget: "$50–100k",
|
|
60
|
+
message:
|
|
61
|
+
"We run six clinics and want an app for booking visits and refilling prescriptions. We have an API from our practice software. Hoping to launch in spring.",
|
|
62
|
+
status: "new",
|
|
63
|
+
hoursAgo: 3,
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
name: "Marcus Iyer",
|
|
67
|
+
email: "marcus@northwind.example",
|
|
68
|
+
company: "Northwind Logistics",
|
|
69
|
+
projectType: "Existing product",
|
|
70
|
+
budget: "$100k+",
|
|
71
|
+
message:
|
|
72
|
+
"Our dispatch team works out of spreadsheets. We need a web app for load planning with live driver locations. Two engineers in-house can take it over after launch.",
|
|
73
|
+
status: "new",
|
|
74
|
+
hoursAgo: 20,
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
name: "Lena Fischer",
|
|
78
|
+
email: "lena@beaconproperty.example",
|
|
79
|
+
company: "Beacon Property",
|
|
80
|
+
projectType: "Existing product",
|
|
81
|
+
budget: "$25–50k",
|
|
82
|
+
message:
|
|
83
|
+
"Three product teams, three sets of buttons. We want one component library in Figma and React, and help rolling it out.",
|
|
84
|
+
status: "new",
|
|
85
|
+
hoursAgo: 46,
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
name: "Ben Osei",
|
|
89
|
+
email: "ben@quarry.example",
|
|
90
|
+
company: "Quarry Design Co",
|
|
91
|
+
projectType: "Rebrand",
|
|
92
|
+
budget: "$25–50k",
|
|
93
|
+
message: "Looking for a partner to build our new marketing site in a headless CMS. Designs are about 70% done.",
|
|
94
|
+
status: "booked",
|
|
95
|
+
hoursAgo: 120,
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
name: "Tom Alvarez",
|
|
99
|
+
email: "tom@riverbed.example",
|
|
100
|
+
company: "Riverbed Coffee",
|
|
101
|
+
projectType: "New product (0→1)",
|
|
102
|
+
budget: "Let's talk",
|
|
103
|
+
message: "A loyalty app for our four cafes. Punch card, order ahead, and push offers.",
|
|
104
|
+
status: "declined",
|
|
105
|
+
hoursAgo: 200,
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
name: "Simone Clarke",
|
|
109
|
+
email: "simone@atlasfitness.example",
|
|
110
|
+
company: "Atlas Fitness",
|
|
111
|
+
projectType: "New product (0→1)",
|
|
112
|
+
budget: "$50–100k",
|
|
113
|
+
message:
|
|
114
|
+
"Members book classes through a third-party tool that charges per booking. We want our own booking and billing, with a coach view for check-ins.",
|
|
115
|
+
status: "new",
|
|
116
|
+
hoursAgo: 70,
|
|
117
|
+
},
|
|
118
|
+
];
|
|
@@ -68,8 +68,10 @@ export type InvoiceSeed = {
|
|
|
68
68
|
// Line items drive the total — `amountCents` is computed from them on seed.
|
|
69
69
|
lineItems: { description: string; quantity: number; unitCents: number }[];
|
|
70
70
|
status?: "draft" | "sent" | "paid" | "overdue";
|
|
71
|
-
|
|
72
|
-
|
|
71
|
+
// Days before the seed runs, so demo invoices stay current: an invoice
|
|
72
|
+
// issued 12 days ago with 30-day terms is always "due in 18 days".
|
|
73
|
+
issuedDaysAgo?: number;
|
|
74
|
+
termsDays?: number;
|
|
73
75
|
};
|
|
74
76
|
|
|
75
77
|
// The studio's billing identity — the "from" block + terms on every invoice and
|
|
@@ -89,7 +91,8 @@ export type AgencyConfig = BaseConfig & {
|
|
|
89
91
|
};
|
|
90
92
|
// Seeds the public Capacity row on first visit; after that it lives in the DB
|
|
91
93
|
// and the owner manages it from the dashboard. `openSlots` is the number the
|
|
92
|
-
// hero shows live; `label` is the booking window it refers to
|
|
94
|
+
// hero shows live; `label` is the booking window it refers to ("" = the
|
|
95
|
+
// next bookable quarter).
|
|
93
96
|
capacity: { label: string; openSlots: number };
|
|
94
97
|
services: { headline: string; items: Service[] };
|
|
95
98
|
work: { headline: string; items: CaseStudy[] };
|
|
@@ -151,7 +154,10 @@ export const siteConfig: AgencyConfig = {
|
|
|
151
154
|
secondaryCtaLabel: "See our work",
|
|
152
155
|
},
|
|
153
156
|
|
|
154
|
-
|
|
157
|
+
// `label` empty = the quarter a new project could start in, worked out from
|
|
158
|
+
// today's date (lib/agency.ts → bookingWindow). Set a fixed label only for a window with a
|
|
159
|
+
// real end date.
|
|
160
|
+
capacity: { label: "", openSlots: 3 },
|
|
155
161
|
|
|
156
162
|
services: {
|
|
157
163
|
headline: "What we do",
|
|
@@ -283,8 +289,8 @@ export const siteConfig: AgencyConfig = {
|
|
|
283
289
|
{ description: "Product design & iOS build — Ledger (12-week engagement)", quantity: 1, unitCents: 4800000 },
|
|
284
290
|
],
|
|
285
291
|
status: "paid",
|
|
286
|
-
|
|
287
|
-
|
|
292
|
+
issuedDaysAgo: 240,
|
|
293
|
+
termsDays: 30,
|
|
288
294
|
},
|
|
289
295
|
{
|
|
290
296
|
number: "INV-002",
|
|
@@ -295,19 +301,19 @@ export const siteConfig: AgencyConfig = {
|
|
|
295
301
|
{ description: "Discovery & scoping sprint", quantity: 1, unitCents: 700000 },
|
|
296
302
|
],
|
|
297
303
|
status: "paid",
|
|
298
|
-
|
|
299
|
-
|
|
304
|
+
issuedDaysAgo: 150,
|
|
305
|
+
termsDays: 30,
|
|
300
306
|
},
|
|
301
307
|
{
|
|
302
308
|
number: "INV-003",
|
|
303
309
|
client: "Tom Reyes",
|
|
304
310
|
projectSlug: "atlas-health",
|
|
305
311
|
lineItems: [
|
|
306
|
-
{ description: "
|
|
312
|
+
{ description: "Monthly maintenance retainer", quantity: 1, unitCents: 850000 },
|
|
307
313
|
],
|
|
308
314
|
status: "sent",
|
|
309
|
-
|
|
310
|
-
|
|
315
|
+
issuedDaysAgo: 12,
|
|
316
|
+
termsDays: 30,
|
|
311
317
|
},
|
|
312
318
|
{
|
|
313
319
|
number: "INV-004",
|
|
@@ -318,8 +324,8 @@ export const siteConfig: AgencyConfig = {
|
|
|
318
324
|
{ description: "Marketing site design & build", quantity: 1, unitCents: 1000000 },
|
|
319
325
|
],
|
|
320
326
|
status: "overdue",
|
|
321
|
-
|
|
322
|
-
|
|
327
|
+
issuedDaysAgo: 64,
|
|
328
|
+
termsDays: 30,
|
|
323
329
|
},
|
|
324
330
|
],
|
|
325
331
|
},
|
|
Binary file
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { bookingWindow, isWideCard, quarterLabel } from "../lib/agency";
|
|
3
|
+
import { DEMO_INQUIRIES, daysAgoDate, demoDataEnabled } from "../lib/demo";
|
|
4
|
+
import { siteConfig } from "../lib/site.config";
|
|
5
|
+
|
|
6
|
+
describe("booking window", () => {
|
|
7
|
+
test("an empty label means the quarter a project could start in", () => {
|
|
8
|
+
expect(quarterLabel(new Date(2026, 8, 26).getTime())).toBe("Q3 2026");
|
|
9
|
+
expect(quarterLabel(new Date(2026, 9, 1).getTime())).toBe("Q4 2026");
|
|
10
|
+
expect(bookingWindow("", new Date(2027, 0, 5).getTime())).toBe("Q1 2027");
|
|
11
|
+
expect(bookingWindow(" ", new Date(2027, 0, 5).getTime())).toBe("Q1 2027");
|
|
12
|
+
// Three weeks from the end of a quarter, the window moves to the next one.
|
|
13
|
+
expect(bookingWindow("", new Date(2026, 8, 5).getTime())).toBe("Q3 2026");
|
|
14
|
+
expect(bookingWindow("", new Date(2026, 8, 26).getTime())).toBe("Q4 2026");
|
|
15
|
+
expect(bookingWindow("", new Date(2026, 11, 20).getTime())).toBe("Q1 2027");
|
|
16
|
+
});
|
|
17
|
+
test("a fixed label wins", () => {
|
|
18
|
+
expect(bookingWindow("January", Date.now())).toBe("January");
|
|
19
|
+
});
|
|
20
|
+
test("the shipped config does not pin a quarter", () => {
|
|
21
|
+
expect(siteConfig.capacity.label).toBe("");
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
describe("work grid", () => {
|
|
26
|
+
test("an odd count widens the first card so no card sits alone on the last row", () => {
|
|
27
|
+
expect([0, 1, 2].map((i) => isWideCard(i, 3))).toEqual([true, false, false]);
|
|
28
|
+
expect([0, 1, 2, 3].map((i) => isWideCard(i, 4))).toEqual([false, false, false, false]);
|
|
29
|
+
expect(isWideCard(0, 1)).toBe(false);
|
|
30
|
+
});
|
|
31
|
+
test("the shipped homepage has three selected case studies, so the rule applies", () => {
|
|
32
|
+
expect(siteConfig.work.items.filter((w) => w.selected).length % 2).toBe(1);
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
describe("demo data", () => {
|
|
37
|
+
test("only under `pylon dev` unless PYLON_DEMO_DATA says otherwise", () => {
|
|
38
|
+
expect(demoDataEnabled({ PYLON_DEV_MODE: "1", PYLON_DEV_WATCH_DIR: "/src/app" })).toBe(true);
|
|
39
|
+
// The Docker base image turns dev mode on by default; without `pylon dev` it must not seed.
|
|
40
|
+
expect(demoDataEnabled({ PYLON_DEV_MODE: "true" })).toBe(false);
|
|
41
|
+
expect(demoDataEnabled({ PYLON_DEV_MODE: "false" })).toBe(false);
|
|
42
|
+
expect(demoDataEnabled({})).toBe(false);
|
|
43
|
+
expect(demoDataEnabled({ PYLON_DEV_MODE: "1", PYLON_DEV_WATCH_DIR: "/src/app", PYLON_DEMO_DATA: "0" })).toBe(false);
|
|
44
|
+
});
|
|
45
|
+
test("demo leads use the contact form's own options", () => {
|
|
46
|
+
for (const q of DEMO_INQUIRIES) {
|
|
47
|
+
expect(siteConfig.contact.projectTypes).toContain(q.projectType);
|
|
48
|
+
expect(siteConfig.contact.budgets).toContain(q.budget);
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
test("demo invoice dates are relative to today", () => {
|
|
52
|
+
const now = new Date(2026, 8, 26).getTime();
|
|
53
|
+
expect(daysAgoDate(12, now)).toBe("2026-09-14");
|
|
54
|
+
expect(daysAgoDate(-18, now)).toBe("2026-10-14");
|
|
55
|
+
for (const inv of siteConfig.backoffice.invoices) expect(inv.issuedDaysAgo).toBeGreaterThan(0);
|
|
56
|
+
});
|
|
57
|
+
});
|
|
@@ -1,33 +1,19 @@
|
|
|
1
1
|
# Copy to `.env` and fill in. `pylon dev` loads `.env` automatically.
|
|
2
2
|
#
|
|
3
|
-
# The app
|
|
4
|
-
#
|
|
5
|
-
# through the built-in POST /api/ai/stream endpoint, so your API key stays on
|
|
6
|
-
# the server and never reaches the browser.
|
|
3
|
+
# The app runs with no config: visitors can chat as guests and messages are
|
|
4
|
+
# saved. Replies need a model provider key. The key stays on the server.
|
|
7
5
|
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
#
|
|
11
|
-
#
|
|
12
|
-
#
|
|
13
|
-
# PYLON_AI_API_KEY=sk-ant-...
|
|
14
|
-
# PYLON_AI_MODEL=claude-sonnet-4-6
|
|
6
|
+
# Model provider (required for replies)
|
|
7
|
+
# PYLON_LLM_PROVIDER "anthropic" | "openai"
|
|
8
|
+
# ANTHROPIC_API_KEY or OPENAI_API_KEY
|
|
9
|
+
# PYLON_LLM_PROVIDER=anthropic
|
|
10
|
+
# ANTHROPIC_API_KEY=sk-ant-...
|
|
15
11
|
|
|
16
|
-
#
|
|
17
|
-
#
|
|
18
|
-
#
|
|
19
|
-
#
|
|
20
|
-
#
|
|
21
|
-
# PYLON_AI_MODELS_ALLOWED=claude-sonnet-4-6,claude-opus-4-8,claude-haiku-4-5
|
|
12
|
+
# OpenAI-compatible gateway (optional), such as OpenRouter:
|
|
13
|
+
# PYLON_LLM_PROVIDER=openai
|
|
14
|
+
# PYLON_LLM_BASE_URL=https://openrouter.ai/api
|
|
15
|
+
# OPENAI_API_KEY=<gateway key>
|
|
16
|
+
# Then list the gateway's model ids in lib/site.config.ts (chat.models).
|
|
22
17
|
|
|
23
|
-
#
|
|
24
|
-
# /api/ai/stream talks to ONE provider. To offer Anthropic + OpenAI + others in
|
|
25
|
-
# the same picker, route through an OpenAI-compatible gateway like OpenRouter:
|
|
26
|
-
# PYLON_AI_PROVIDER=custom
|
|
27
|
-
# PYLON_AI_BASE_URL=https://openrouter.ai/api/v1
|
|
28
|
-
# PYLON_AI_API_KEY=<openrouter key>
|
|
29
|
-
# then set chat.models (config) + PYLON_AI_MODELS_ALLOWED to that gateway's slugs
|
|
30
|
-
# (e.g. anthropic/claude-sonnet-4, openai/gpt-4o, google/gemini-2.5-pro).
|
|
31
|
-
|
|
32
|
-
# ── Site URL (optional) ──────────────────────────────────────────────────────
|
|
18
|
+
# Site URL (optional)
|
|
33
19
|
# SITE_URL=https://yourchat.com
|
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
# __APP_NAME__
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
API key never reaches the browser. Conversations are sync-backed and
|
|
8
|
-
owner-scoped; a chat sent in one tab appears in the other.
|
|
3
|
+
Lumen, a streaming AI assistant built with [Pylon](https://pylonsync.com).
|
|
4
|
+
Visitors chat right away in a guest session. Signing in moves their chats to
|
|
5
|
+
the account. Conversations sync live across tabs and devices, and the provider
|
|
6
|
+
key stays on the server.
|
|
9
7
|
|
|
10
8
|
## Develop
|
|
11
9
|
|
|
@@ -13,80 +11,75 @@ owner-scoped; a chat sent in one tab appears in the other.
|
|
|
13
11
|
__RUN_DEV__
|
|
14
12
|
```
|
|
15
13
|
|
|
16
|
-
Open http://localhost:4321.
|
|
17
|
-
|
|
18
|
-
messages
|
|
19
|
-
|
|
20
|
-
## Configure the model
|
|
14
|
+
Open http://localhost:4321. With no provider key, the app shows the setup
|
|
15
|
+
steps, seeds three example conversations (labeled as examples and read-only),
|
|
16
|
+
and still stores the messages you send.
|
|
21
17
|
|
|
22
|
-
|
|
23
|
-
still boots and displays a configuration notice:
|
|
18
|
+
## Add a model provider
|
|
24
19
|
|
|
25
20
|
```bash
|
|
26
21
|
# .env
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
PYLON_AI_MODEL=claude-sonnet-4-6 # default when none is picked
|
|
22
|
+
PYLON_LLM_PROVIDER=anthropic
|
|
23
|
+
ANTHROPIC_API_KEY=sk-ant-...
|
|
30
24
|
```
|
|
31
25
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
The composer has a **model picker** — its options live in `lib/site.config.ts`
|
|
38
|
-
(`chat.models`, each with a provider label), and the chosen id is sent per
|
|
39
|
-
request. Client-chosen models must be allow-listed server-side:
|
|
26
|
+
Restart `pylon dev`. For OpenAI, set `PYLON_LLM_PROVIDER=openai` and
|
|
27
|
+
`OPENAI_API_KEY`, then replace the ids in `chat.models` in
|
|
28
|
+
`lib/site.config.ts`. For an OpenAI-compatible gateway such as OpenRouter,
|
|
29
|
+
also set `PYLON_LLM_BASE_URL` (for example `https://openrouter.ai/api`) and use
|
|
30
|
+
the gateway's model ids.
|
|
40
31
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
`/api/ai/stream` talks to **one** provider. To offer models from **multiple
|
|
46
|
-
providers** in one picker, route through an OpenAI-compatible gateway like
|
|
47
|
-
[OpenRouter](https://openrouter.ai):
|
|
48
|
-
|
|
49
|
-
```bash
|
|
50
|
-
PYLON_AI_PROVIDER=custom
|
|
51
|
-
PYLON_AI_BASE_URL=https://openrouter.ai/api/v1
|
|
52
|
-
PYLON_AI_API_KEY=<openrouter key>
|
|
53
|
-
```
|
|
54
|
-
|
|
55
|
-
then set `chat.models` + `PYLON_AI_MODELS_ALLOWED` to the gateway's slugs
|
|
56
|
-
(`anthropic/claude-sonnet-4`, `openai/gpt-4o`, `google/gemini-2.5-pro`, …).
|
|
32
|
+
The model list in `lib/site.config.ts` is the allowlist. `app.ts` passes it to
|
|
33
|
+
`llm({ allowedModels })`, and `respond` refuses any other id.
|
|
57
34
|
|
|
58
35
|
## How it works
|
|
59
36
|
|
|
60
|
-
- **
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
- **
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
- **
|
|
68
|
-
|
|
69
|
-
|
|
37
|
+
- **Replies.** `functions/respond.ts` is a streaming action. It calls
|
|
38
|
+
`ctx.llm.stream` and writes each token to `ctx.stream`. The client reads it
|
|
39
|
+
with `streamFn("respond", …)`.
|
|
40
|
+
- **Other tabs.** `_beginTurn` stores the call's resumable stream id on the
|
|
41
|
+
assistant message. Another tab (or this one after a reload) attaches with
|
|
42
|
+
`resumeStream(streamId)` and shows the same tokens. The final text is written
|
|
43
|
+
to the message row, which syncs everywhere.
|
|
44
|
+
- **Stop and regenerate.** Stop stores the text shown so far with status
|
|
45
|
+
`stopped`. Regenerate removes the last reply and writes a new one.
|
|
46
|
+
- **Guests.** `<EnsureGuest>` creates a guest session on first visit. Every
|
|
47
|
+
function the chat calls accepts guests (`auth: "guest"`).
|
|
48
|
+
- **Sign-in keeps history.** Before signing in, the guest gets a one-time code
|
|
49
|
+
from `prepareGuestClaim`. After sign-in, `claimGuestHistory` uses it to move
|
|
50
|
+
the guest's conversations to the account.
|
|
51
|
+
- **Limits.** `repliesPerHour` in `lib/site.config.ts` caps replies per rolling
|
|
52
|
+
hour, lower for guests than for accounts.
|
|
70
53
|
|
|
71
54
|
## Privacy
|
|
72
55
|
|
|
73
|
-
`Conversation` and `Message`
|
|
74
|
-
|
|
75
|
-
|
|
56
|
+
`Conversation` and `Message` rows are readable only by their owner. Clients can
|
|
57
|
+
rename their own conversations; every other write goes through a server
|
|
58
|
+
function that checks ownership. `GuestClaim` is never readable by clients, and
|
|
59
|
+
only a hash of each claim code is stored. `User.passwordHash` is `serverOnly`.
|
|
76
60
|
|
|
77
61
|
## Rebrand it
|
|
78
62
|
|
|
79
|
-
The name, colors, system prompt,
|
|
80
|
-
|
|
63
|
+
The name, colors, system prompt, suggestions, models, and limits live in
|
|
64
|
+
`lib/site.config.ts`. Replace or delete the examples in `lib/examples.ts`.
|
|
81
65
|
|
|
82
66
|
## Layout
|
|
83
67
|
|
|
84
68
|
```
|
|
85
|
-
app.ts
|
|
86
|
-
lib/site.config.ts
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
69
|
+
app.ts entities, policies, llm() allowlist, fonts
|
|
70
|
+
lib/site.config.ts brand, prompt, suggestions, models, limits
|
|
71
|
+
lib/chat.ts history, titles, day groups, error copy (tested)
|
|
72
|
+
lib/markdown.ts Markdown parser for replies (tested)
|
|
73
|
+
functions/respond.ts streams a reply with ctx.llm.stream
|
|
74
|
+
app/(chat)/chat-client.tsx the only file that talks to Pylon on the client
|
|
75
|
+
components/chat/ sidebar, thread, composer, setup card
|
|
76
|
+
app/login/ optional sign-in
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Test
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
pylon test
|
|
90
83
|
```
|
|
91
84
|
|
|
92
85
|
## Deploy
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { type Metadata, type PageProps } from "@pylonsync/react";
|
|
3
|
+
import { ChatApp } from "../../chat-client";
|
|
4
|
+
import { siteConfig } from "@/lib/site.config";
|
|
5
|
+
import { brandIcon } from "@/lib/brand-icon";
|
|
6
|
+
|
|
7
|
+
export const metadata: Metadata = {
|
|
8
|
+
title: siteConfig.seo.title,
|
|
9
|
+
robots: "noindex",
|
|
10
|
+
icons: { icon: brandIcon },
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
// `/c/:id`: one conversation. The id is only a starting point; the client
|
|
14
|
+
// loads the conversation through the owner-scoped live query, so another
|
|
15
|
+
// visitor's id shows nothing.
|
|
16
|
+
export default function ConversationPage({ auth, params }: PageProps<{ id: string }>) {
|
|
17
|
+
const signedIn = Boolean(auth.user_id) && !auth.is_guest && !auth.user_id?.startsWith("guest_");
|
|
18
|
+
return <ChatApp initialConversationId={params.id} signedInUserId={signedIn ? auth.user_id : null} />;
|
|
19
|
+
}
|