@pylonsync/create-pylon 0.3.274 → 0.3.276
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 +80 -0
- package/package.json +1 -1
- package/templates/ARCHETYPES.md +339 -0
- package/templates/agency/.env.example +12 -0
- package/templates/agency/AGENTS.md +61 -0
- package/templates/agency/README.md +90 -0
- package/templates/agency/app/auth-form.tsx +129 -0
- package/templates/agency/app/contact-form.tsx +258 -0
- package/templates/agency/app/dashboard/dashboard-client.tsx +1440 -0
- package/templates/agency/app/dashboard/page.tsx +70 -0
- package/templates/agency/app/error.tsx +26 -0
- package/templates/agency/app/globals.css +148 -0
- package/templates/agency/app/layout.tsx +174 -0
- package/templates/agency/app/login/page.tsx +39 -0
- package/templates/agency/app/not-found.tsx +19 -0
- package/templates/agency/app/page.tsx +249 -0
- package/templates/agency/app/robots.ts +12 -0
- package/templates/agency/app/seeder.tsx +26 -0
- package/templates/agency/app/sitemap.ts +9 -0
- package/templates/agency/app/work/[slug]/page.tsx +182 -0
- package/templates/agency/app/work/page.tsx +83 -0
- package/templates/agency/app.ts +284 -0
- package/templates/agency/components/marketing.tsx +187 -0
- package/templates/agency/components/section-scroller.tsx +35 -0
- package/templates/agency/components/ui/button.tsx +56 -0
- package/templates/agency/components/ui/card.tsx +90 -0
- package/templates/agency/components.json +20 -0
- package/templates/agency/functions/bookInquiry.ts +42 -0
- package/templates/agency/functions/clientsForOwner.ts +27 -0
- package/templates/agency/functions/declineInquiry.ts +41 -0
- package/templates/agency/functions/deleteClient.ts +27 -0
- package/templates/agency/functions/deleteInvoice.ts +19 -0
- package/templates/agency/functions/deleteProject.ts +20 -0
- package/templates/agency/functions/inquiriesForOwner.ts +31 -0
- package/templates/agency/functions/invoicesForOwner.ts +27 -0
- package/templates/agency/functions/seedCapacity.ts +26 -0
- package/templates/agency/functions/seedProjects.ts +41 -0
- package/templates/agency/functions/seedStudioBackoffice.ts +74 -0
- package/templates/agency/functions/setCapacity.ts +32 -0
- package/templates/agency/functions/setInvoiceStatus.ts +27 -0
- package/templates/agency/functions/setProjectFlags.ts +35 -0
- package/templates/agency/functions/submitInquiry.ts +55 -0
- package/templates/agency/functions/upsertClient.ts +73 -0
- package/templates/agency/functions/upsertInvoice.ts +113 -0
- package/templates/agency/functions/upsertProject.ts +97 -0
- package/templates/agency/gitignore +10 -0
- package/templates/agency/lib/agency.ts +189 -0
- package/templates/agency/lib/invoice-pdf.tsx +174 -0
- package/templates/agency/lib/owner.ts +26 -0
- package/templates/agency/lib/site.config.ts +418 -0
- package/templates/agency/lib/utils.ts +10 -0
- package/templates/agency/package.json +35 -0
- package/templates/agency/tsconfig.json +18 -0
- package/templates/ai-chat/.env.example +33 -0
- package/templates/ai-chat/AGENTS.md +61 -0
- package/templates/ai-chat/README.md +99 -0
- package/templates/ai-chat/app/auth-form.tsx +124 -0
- package/templates/ai-chat/app/chat-client.tsx +727 -0
- package/templates/ai-chat/app/error.tsx +26 -0
- package/templates/ai-chat/app/globals.css +148 -0
- package/templates/ai-chat/app/layout.tsx +75 -0
- package/templates/ai-chat/app/login/page.tsx +39 -0
- package/templates/ai-chat/app/not-found.tsx +19 -0
- package/templates/ai-chat/app/page.tsx +23 -0
- package/templates/ai-chat/app.ts +121 -0
- package/templates/ai-chat/components.json +20 -0
- package/templates/ai-chat/functions/deleteConversation.ts +33 -0
- package/templates/ai-chat/gitignore +10 -0
- package/templates/ai-chat/lib/site.config.ts +103 -0
- package/templates/ai-chat/lib/utils.ts +10 -0
- package/templates/ai-chat/package.json +34 -0
- package/templates/ai-chat/tsconfig.json +18 -0
- package/templates/ai-studio/.env.example +19 -0
- package/templates/ai-studio/AGENTS.md +61 -0
- package/templates/ai-studio/README.md +83 -0
- package/templates/ai-studio/app/auth-form.tsx +124 -0
- package/templates/ai-studio/app/error.tsx +26 -0
- package/templates/ai-studio/app/globals.css +148 -0
- package/templates/ai-studio/app/layout.tsx +75 -0
- package/templates/ai-studio/app/login/page.tsx +39 -0
- package/templates/ai-studio/app/not-found.tsx +19 -0
- package/templates/ai-studio/app/page.tsx +34 -0
- package/templates/ai-studio/app/studio-client.tsx +357 -0
- package/templates/ai-studio/app.ts +108 -0
- package/templates/ai-studio/components.json +20 -0
- package/templates/ai-studio/functions/_getGeneration.ts +25 -0
- package/templates/ai-studio/functions/_updateGeneration.ts +37 -0
- package/templates/ai-studio/functions/generate.ts +42 -0
- package/templates/ai-studio/functions/pollGeneration.ts +134 -0
- package/templates/ai-studio/gitignore +10 -0
- package/templates/ai-studio/lib/site.config.ts +80 -0
- package/templates/ai-studio/lib/studio.ts +52 -0
- package/templates/ai-studio/lib/utils.ts +10 -0
- package/templates/ai-studio/package.json +34 -0
- package/templates/ai-studio/tsconfig.json +18 -0
- package/templates/creator/.env.example +12 -0
- package/templates/creator/AGENTS.md +61 -0
- package/templates/creator/README.md +67 -0
- package/templates/creator/app/auth-form.tsx +129 -0
- package/templates/creator/app/dashboard/dashboard-client.tsx +297 -0
- package/templates/creator/app/dashboard/page.tsx +70 -0
- package/templates/creator/app/error.tsx +26 -0
- package/templates/creator/app/globals.css +148 -0
- package/templates/creator/app/layout.tsx +160 -0
- package/templates/creator/app/login/page.tsx +39 -0
- package/templates/creator/app/newsletter-signup.tsx +162 -0
- package/templates/creator/app/not-found.tsx +19 -0
- package/templates/creator/app/page.tsx +160 -0
- package/templates/creator/app/robots.ts +12 -0
- package/templates/creator/app/sitemap.ts +9 -0
- package/templates/creator/app.ts +134 -0
- package/templates/creator/components/marketing.tsx +148 -0
- package/templates/creator/components/section-scroller.tsx +35 -0
- package/templates/creator/components/ui/button.tsx +56 -0
- package/templates/creator/components/ui/card.tsx +90 -0
- package/templates/creator/components.json +20 -0
- package/templates/creator/functions/subscribe.ts +82 -0
- package/templates/creator/functions/subscriberStats.ts +75 -0
- package/templates/creator/gitignore +10 -0
- package/templates/creator/lib/owner.ts +26 -0
- package/templates/creator/lib/site.config.ts +173 -0
- package/templates/creator/lib/stats.ts +30 -0
- package/templates/creator/lib/utils.ts +10 -0
- package/templates/creator/package.json +34 -0
- package/templates/creator/tsconfig.json +18 -0
- package/templates/default/app/layout.tsx +26 -27
- package/templates/default/app/page.tsx +90 -274
- package/templates/default/lib/products.ts +9 -122
- package/templates/default/lib/site.config.ts +739 -0
- package/templates/default/lib/site.ts +14 -261
- package/templates/directory/.env.example +12 -0
- package/templates/directory/AGENTS.md +61 -0
- package/templates/directory/README.md +80 -0
- package/templates/directory/app/auth-form.tsx +129 -0
- package/templates/directory/app/dashboard/dashboard-client.tsx +205 -0
- package/templates/directory/app/dashboard/page.tsx +70 -0
- package/templates/directory/app/directory-browse.tsx +328 -0
- package/templates/directory/app/error.tsx +26 -0
- package/templates/directory/app/globals.css +148 -0
- package/templates/directory/app/layout.tsx +171 -0
- package/templates/directory/app/login/page.tsx +39 -0
- package/templates/directory/app/not-found.tsx +19 -0
- package/templates/directory/app/page.tsx +50 -0
- package/templates/directory/app/robots.ts +12 -0
- package/templates/directory/app/sitemap.ts +9 -0
- package/templates/directory/app/submit/page.tsx +30 -0
- package/templates/directory/app/submit-form.tsx +151 -0
- package/templates/directory/app.ts +146 -0
- package/templates/directory/components/marketing.tsx +148 -0
- package/templates/directory/components/section-scroller.tsx +35 -0
- package/templates/directory/components/ui/button.tsx +56 -0
- package/templates/directory/components/ui/card.tsx +90 -0
- package/templates/directory/components.json +20 -0
- package/templates/directory/functions/approveSubmission.ts +45 -0
- package/templates/directory/functions/rejectSubmission.ts +20 -0
- package/templates/directory/functions/seedListings.ts +33 -0
- package/templates/directory/functions/submissionsForOwner.ts +29 -0
- package/templates/directory/functions/submitListing.ts +63 -0
- package/templates/directory/functions/upvote.ts +24 -0
- package/templates/directory/gitignore +10 -0
- package/templates/directory/lib/directory.ts +45 -0
- package/templates/directory/lib/owner.ts +26 -0
- package/templates/directory/lib/site.config.ts +130 -0
- package/templates/directory/lib/utils.ts +10 -0
- package/templates/directory/package.json +34 -0
- package/templates/directory/tsconfig.json +18 -0
- package/templates/local-service/.env.example +12 -0
- package/templates/local-service/AGENTS.md +61 -0
- package/templates/local-service/README.md +82 -0
- package/templates/local-service/app/auth-form.tsx +129 -0
- package/templates/local-service/app/booking-widget.tsx +399 -0
- package/templates/local-service/app/dashboard/dashboard-client.tsx +304 -0
- package/templates/local-service/app/dashboard/page.tsx +63 -0
- package/templates/local-service/app/error.tsx +26 -0
- package/templates/local-service/app/globals.css +148 -0
- package/templates/local-service/app/layout.tsx +151 -0
- package/templates/local-service/app/login/page.tsx +39 -0
- package/templates/local-service/app/not-found.tsx +19 -0
- package/templates/local-service/app/page.tsx +233 -0
- package/templates/local-service/app/robots.ts +12 -0
- package/templates/local-service/app/sitemap.ts +9 -0
- package/templates/local-service/app.ts +131 -0
- package/templates/local-service/components/marketing.tsx +162 -0
- package/templates/local-service/components/section-scroller.tsx +35 -0
- package/templates/local-service/components/ui/button.tsx +56 -0
- package/templates/local-service/components/ui/card.tsx +90 -0
- package/templates/local-service/components.json +20 -0
- package/templates/local-service/functions/bookingsForOwner.ts +30 -0
- package/templates/local-service/functions/cancelBooking.ts +27 -0
- package/templates/local-service/functions/confirmBooking.ts +18 -0
- package/templates/local-service/functions/createBooking.ts +98 -0
- package/templates/local-service/gitignore +10 -0
- package/templates/local-service/lib/booking.ts +24 -0
- package/templates/local-service/lib/owner.ts +26 -0
- package/templates/local-service/lib/site.config.ts +232 -0
- package/templates/local-service/lib/slots.ts +97 -0
- package/templates/local-service/lib/utils.ts +10 -0
- package/templates/local-service/package.json +34 -0
- package/templates/local-service/tsconfig.json +18 -0
- package/templates/marketplace/.env.example +9 -0
- package/templates/marketplace/AGENTS.md +61 -0
- package/templates/marketplace/README.md +78 -0
- package/templates/marketplace/app/_components/CategoryIcon.tsx +40 -0
- package/templates/marketplace/app/error.tsx +26 -0
- package/templates/marketplace/app/globals.css +64 -0
- package/templates/marketplace/app/layout.tsx +60 -0
- package/templates/marketplace/app/listing/[id]/page.tsx +163 -0
- package/templates/marketplace/app/me/page.tsx +15 -0
- package/templates/marketplace/app/not-found.tsx +20 -0
- package/templates/marketplace/app/page.tsx +159 -0
- package/templates/marketplace/app/robots.ts +12 -0
- package/templates/marketplace/app/sell/page.tsx +26 -0
- package/templates/marketplace/app/sitemap.ts +14 -0
- package/templates/marketplace/app.ts +190 -0
- package/templates/marketplace/client/AuthNav.tsx +46 -0
- package/templates/marketplace/client/LiveTicker.tsx +104 -0
- package/templates/marketplace/client/LoginCard.tsx +130 -0
- package/templates/marketplace/client/MarketProvider.tsx +148 -0
- package/templates/marketplace/client/MyMarket.tsx +180 -0
- package/templates/marketplace/client/OfferPanel.tsx +355 -0
- package/templates/marketplace/client/SeedOnEmpty.tsx +26 -0
- package/templates/marketplace/client/SellForm.tsx +160 -0
- package/templates/marketplace/client/WatchButton.tsx +88 -0
- package/templates/marketplace/client/market.ts +341 -0
- package/templates/marketplace/functions/buyNow.ts +78 -0
- package/templates/marketplace/functions/makeOffer.ts +65 -0
- package/templates/marketplace/functions/respondToOffer.ts +62 -0
- package/templates/marketplace/functions/seedMarket.ts +90 -0
- package/templates/marketplace/gitignore +10 -0
- package/templates/marketplace/package.json +35 -0
- package/templates/marketplace/tsconfig.json +14 -0
- package/templates/marketplace/ui/badge.tsx +30 -0
- package/templates/marketplace/ui/button.tsx +49 -0
- package/templates/marketplace/ui/card.tsx +48 -0
- package/templates/marketplace/ui/input.tsx +17 -0
- package/templates/marketplace/ui/label.tsx +18 -0
- package/templates/marketplace/ui/textarea.tsx +17 -0
- package/templates/marketplace/ui/tokens.css +32 -0
- package/templates/marketplace/ui/utils.ts +6 -0
- package/templates/restaurant/.env.example +12 -0
- package/templates/restaurant/AGENTS.md +61 -0
- package/templates/restaurant/README.md +77 -0
- package/templates/restaurant/app/auth-form.tsx +129 -0
- package/templates/restaurant/app/dashboard/dashboard-client.tsx +263 -0
- package/templates/restaurant/app/dashboard/page.tsx +59 -0
- package/templates/restaurant/app/error.tsx +26 -0
- package/templates/restaurant/app/globals.css +148 -0
- package/templates/restaurant/app/layout.tsx +151 -0
- package/templates/restaurant/app/login/page.tsx +39 -0
- package/templates/restaurant/app/not-found.tsx +19 -0
- package/templates/restaurant/app/page.tsx +194 -0
- package/templates/restaurant/app/reservation-widget.tsx +359 -0
- package/templates/restaurant/app/robots.ts +12 -0
- package/templates/restaurant/app/sitemap.ts +9 -0
- package/templates/restaurant/app.ts +115 -0
- package/templates/restaurant/components/marketing.tsx +162 -0
- package/templates/restaurant/components/section-scroller.tsx +35 -0
- package/templates/restaurant/components/ui/button.tsx +56 -0
- package/templates/restaurant/components/ui/card.tsx +90 -0
- package/templates/restaurant/components.json +20 -0
- package/templates/restaurant/functions/cancelReservation.ts +26 -0
- package/templates/restaurant/functions/confirmReservation.ts +17 -0
- package/templates/restaurant/functions/createReservation.ts +92 -0
- package/templates/restaurant/functions/reservationsForOwner.ts +28 -0
- package/templates/restaurant/gitignore +10 -0
- package/templates/restaurant/lib/owner.ts +26 -0
- package/templates/restaurant/lib/reservation.ts +22 -0
- package/templates/restaurant/lib/site.config.ts +218 -0
- package/templates/restaurant/lib/slots.ts +55 -0
- package/templates/restaurant/lib/utils.ts +10 -0
- package/templates/restaurant/package.json +34 -0
- package/templates/restaurant/tsconfig.json +18 -0
- package/templates/shop/.env.example +32 -0
- package/templates/shop/AGENTS.md +61 -0
- package/templates/shop/README.md +102 -0
- package/templates/shop/app/auth-form.tsx +129 -0
- package/templates/shop/app/dashboard/dashboard-client.tsx +264 -0
- package/templates/shop/app/dashboard/page.tsx +59 -0
- package/templates/shop/app/error.tsx +26 -0
- package/templates/shop/app/globals.css +148 -0
- package/templates/shop/app/layout.tsx +160 -0
- package/templates/shop/app/login/page.tsx +39 -0
- package/templates/shop/app/not-found.tsx +19 -0
- package/templates/shop/app/page.tsx +95 -0
- package/templates/shop/app/robots.ts +12 -0
- package/templates/shop/app/shop-client.tsx +436 -0
- package/templates/shop/app/sitemap.ts +9 -0
- package/templates/shop/app/success/page.tsx +33 -0
- package/templates/shop/app.ts +134 -0
- package/templates/shop/components/marketing.tsx +96 -0
- package/templates/shop/components/section-scroller.tsx +35 -0
- package/templates/shop/components/ui/button.tsx +56 -0
- package/templates/shop/components/ui/card.tsx +90 -0
- package/templates/shop/components.json +20 -0
- package/templates/shop/functions/cancelOrder.ts +33 -0
- package/templates/shop/functions/checkout.ts +130 -0
- package/templates/shop/functions/fulfillOrder.ts +17 -0
- package/templates/shop/functions/markGroupPaid.ts +26 -0
- package/templates/shop/functions/ordersForOwner.ts +28 -0
- package/templates/shop/functions/releaseGroup.ts +36 -0
- package/templates/shop/functions/reserveCart.ts +87 -0
- package/templates/shop/functions/restockProduct.ts +23 -0
- package/templates/shop/functions/seedProducts.ts +30 -0
- package/templates/shop/functions/stripeWebhook.ts +72 -0
- package/templates/shop/gitignore +10 -0
- package/templates/shop/lib/owner.ts +26 -0
- package/templates/shop/lib/shop.ts +45 -0
- package/templates/shop/lib/site.config.ts +198 -0
- package/templates/shop/lib/utils.ts +10 -0
- package/templates/shop/package.json +35 -0
- package/templates/shop/tsconfig.json +18 -0
- package/templates/waitlist/.env.example +12 -0
- package/templates/waitlist/AGENTS.md +61 -0
- package/templates/waitlist/README.md +81 -0
- package/templates/waitlist/app/auth-form.tsx +129 -0
- package/templates/waitlist/app/dashboard/dashboard-client.tsx +297 -0
- package/templates/waitlist/app/dashboard/page.tsx +70 -0
- package/templates/waitlist/app/error.tsx +26 -0
- package/templates/waitlist/app/globals.css +148 -0
- package/templates/waitlist/app/layout.tsx +158 -0
- package/templates/waitlist/app/login/page.tsx +39 -0
- package/templates/waitlist/app/not-found.tsx +19 -0
- package/templates/waitlist/app/page.tsx +119 -0
- package/templates/waitlist/app/robots.ts +12 -0
- package/templates/waitlist/app/sitemap.ts +9 -0
- package/templates/waitlist/app/waitlist-hero.tsx +219 -0
- package/templates/waitlist/app.ts +134 -0
- package/templates/waitlist/components/marketing.tsx +96 -0
- package/templates/waitlist/components/ui/button.tsx +56 -0
- package/templates/waitlist/components/ui/card.tsx +90 -0
- package/templates/waitlist/components.json +20 -0
- package/templates/waitlist/functions/joinWaitlist.ts +82 -0
- package/templates/waitlist/functions/waitlistStats.ts +75 -0
- package/templates/waitlist/gitignore +10 -0
- package/templates/waitlist/lib/owner.ts +26 -0
- package/templates/waitlist/lib/site.config.ts +178 -0
- package/templates/waitlist/lib/stats.ts +30 -0
- package/templates/waitlist/lib/utils.ts +10 -0
- package/templates/waitlist/package.json +34 -0
- package/templates/waitlist/tsconfig.json +18 -0
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { type ErrorBoundaryProps } from "@pylonsync/react";
|
|
3
|
+
|
|
4
|
+
// `app/error.tsx` → the error boundary for this segment. Hydrated + interactive:
|
|
5
|
+
// `reset()` re-attempts the route. The thrown error reaches the client as
|
|
6
|
+
// `{ message, digest }` only — the stack stays in the dev overlay / server logs.
|
|
7
|
+
export default function Error({ error, reset }: ErrorBoundaryProps) {
|
|
8
|
+
return (
|
|
9
|
+
<div className="mx-auto flex min-h-[60vh] max-w-3xl flex-col items-center justify-center px-6 text-center">
|
|
10
|
+
<h1 className="text-2xl font-semibold tracking-tight">Something went wrong</h1>
|
|
11
|
+
<p className="mt-2 text-zinc-500">{error.message}</p>
|
|
12
|
+
{error.digest ? (
|
|
13
|
+
<p className="mt-1 text-xs text-zinc-400">
|
|
14
|
+
Reference: <code>{error.digest}</code>
|
|
15
|
+
</p>
|
|
16
|
+
) : null}
|
|
17
|
+
<button
|
|
18
|
+
type="button"
|
|
19
|
+
onClick={reset}
|
|
20
|
+
className="mt-6 inline-flex h-10 items-center rounded-full bg-zinc-900 px-5 text-sm font-medium text-white transition-colors hover:bg-zinc-700"
|
|
21
|
+
>
|
|
22
|
+
Try again
|
|
23
|
+
</button>
|
|
24
|
+
</div>
|
|
25
|
+
);
|
|
26
|
+
}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
@import "tailwindcss";
|
|
2
|
+
@import "tw-animate-css";
|
|
3
|
+
|
|
4
|
+
/* Tailwind v4 scans these globs for class names. Add more @source lines if you
|
|
5
|
+
put markup elsewhere. The @pylonsync/client line lets its components
|
|
6
|
+
(EnsureGuest, auth helpers) keep any classes they ship. */
|
|
7
|
+
@source "../app/**/*.{tsx,ts,jsx,js}";
|
|
8
|
+
@source "../components/**/*.{tsx,ts,jsx,js}";
|
|
9
|
+
@source "../lib/**/*.{tsx,ts,jsx,js}";
|
|
10
|
+
@source "../node_modules/@pylonsync/client/**/*.{tsx,ts,jsx,js}";
|
|
11
|
+
|
|
12
|
+
@custom-variant dark (&:where(.dark, .dark *));
|
|
13
|
+
|
|
14
|
+
/* shadcn/ui design tokens (new-york / zinc) + the marketing brand accent. The
|
|
15
|
+
three brand vars are defaults — app/layout.tsx overrides them from
|
|
16
|
+
lib/site.config.ts on <html>, so re-theming the whole page is one edit there. */
|
|
17
|
+
:root {
|
|
18
|
+
--radius: 0.625rem;
|
|
19
|
+
--brand: #4f46e5;
|
|
20
|
+
--brand-soft: #eef2ff;
|
|
21
|
+
--paper: #fafafa;
|
|
22
|
+
--background: oklch(1 0 0);
|
|
23
|
+
--foreground: oklch(0.141 0.005 285.823);
|
|
24
|
+
--card: oklch(1 0 0);
|
|
25
|
+
--card-foreground: oklch(0.141 0.005 285.823);
|
|
26
|
+
--popover: oklch(1 0 0);
|
|
27
|
+
--popover-foreground: oklch(0.141 0.005 285.823);
|
|
28
|
+
--primary: oklch(0.21 0.006 285.885);
|
|
29
|
+
--primary-foreground: oklch(0.985 0 0);
|
|
30
|
+
--secondary: oklch(0.967 0.001 286.375);
|
|
31
|
+
--secondary-foreground: oklch(0.21 0.006 285.885);
|
|
32
|
+
--muted: oklch(0.967 0.001 286.375);
|
|
33
|
+
--muted-foreground: oklch(0.552 0.016 285.938);
|
|
34
|
+
--accent: oklch(0.967 0.001 286.375);
|
|
35
|
+
--accent-foreground: oklch(0.21 0.006 285.885);
|
|
36
|
+
--destructive: oklch(0.577 0.245 27.325);
|
|
37
|
+
--border: oklch(0.92 0.004 286.32);
|
|
38
|
+
--input: oklch(0.92 0.004 286.32);
|
|
39
|
+
--ring: oklch(0.705 0.015 286.067);
|
|
40
|
+
--chart-1: oklch(0.646 0.222 41.116);
|
|
41
|
+
--chart-2: oklch(0.6 0.118 184.704);
|
|
42
|
+
--chart-3: oklch(0.398 0.07 227.392);
|
|
43
|
+
--chart-4: oklch(0.828 0.189 84.429);
|
|
44
|
+
--chart-5: oklch(0.769 0.188 70.08);
|
|
45
|
+
--sidebar: oklch(0.985 0 0);
|
|
46
|
+
--sidebar-foreground: oklch(0.141 0.005 285.823);
|
|
47
|
+
--sidebar-primary: oklch(0.21 0.006 285.885);
|
|
48
|
+
--sidebar-primary-foreground: oklch(0.985 0 0);
|
|
49
|
+
--sidebar-accent: oklch(0.967 0.001 286.375);
|
|
50
|
+
--sidebar-accent-foreground: oklch(0.21 0.006 285.885);
|
|
51
|
+
--sidebar-border: oklch(0.92 0.004 286.32);
|
|
52
|
+
--sidebar-ring: oklch(0.705 0.015 286.067);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.dark {
|
|
56
|
+
--background: oklch(0.141 0.005 285.823);
|
|
57
|
+
--foreground: oklch(0.985 0 0);
|
|
58
|
+
--card: oklch(0.21 0.006 285.885);
|
|
59
|
+
--card-foreground: oklch(0.985 0 0);
|
|
60
|
+
--popover: oklch(0.21 0.006 285.885);
|
|
61
|
+
--popover-foreground: oklch(0.985 0 0);
|
|
62
|
+
--primary: oklch(0.92 0.004 286.32);
|
|
63
|
+
--primary-foreground: oklch(0.21 0.006 285.885);
|
|
64
|
+
--secondary: oklch(0.274 0.006 286.033);
|
|
65
|
+
--secondary-foreground: oklch(0.985 0 0);
|
|
66
|
+
--muted: oklch(0.274 0.006 286.033);
|
|
67
|
+
--muted-foreground: oklch(0.705 0.015 286.067);
|
|
68
|
+
--accent: oklch(0.274 0.006 286.033);
|
|
69
|
+
--accent-foreground: oklch(0.985 0 0);
|
|
70
|
+
--destructive: oklch(0.704 0.191 22.216);
|
|
71
|
+
--border: oklch(1 0 0 / 10%);
|
|
72
|
+
--input: oklch(1 0 0 / 15%);
|
|
73
|
+
--ring: oklch(0.552 0.016 285.938);
|
|
74
|
+
--chart-1: oklch(0.488 0.243 264.376);
|
|
75
|
+
--chart-2: oklch(0.696 0.17 162.48);
|
|
76
|
+
--chart-3: oklch(0.769 0.188 70.08);
|
|
77
|
+
--chart-4: oklch(0.627 0.265 303.9);
|
|
78
|
+
--chart-5: oklch(0.645 0.246 16.439);
|
|
79
|
+
--sidebar: oklch(0.21 0.006 285.885);
|
|
80
|
+
--sidebar-foreground: oklch(0.985 0 0);
|
|
81
|
+
--sidebar-primary: oklch(0.488 0.243 264.376);
|
|
82
|
+
--sidebar-primary-foreground: oklch(0.985 0 0);
|
|
83
|
+
--sidebar-accent: oklch(0.274 0.006 286.033);
|
|
84
|
+
--sidebar-accent-foreground: oklch(0.985 0 0);
|
|
85
|
+
--sidebar-border: oklch(1 0 0 / 10%);
|
|
86
|
+
--sidebar-ring: oklch(0.552 0.016 285.938);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
@theme inline {
|
|
90
|
+
--radius-sm: calc(var(--radius) - 4px);
|
|
91
|
+
--radius-md: calc(var(--radius) - 2px);
|
|
92
|
+
--radius-lg: var(--radius);
|
|
93
|
+
--radius-xl: calc(var(--radius) + 4px);
|
|
94
|
+
--color-background: var(--background);
|
|
95
|
+
--color-foreground: var(--foreground);
|
|
96
|
+
--color-card: var(--card);
|
|
97
|
+
--color-card-foreground: var(--card-foreground);
|
|
98
|
+
--color-popover: var(--popover);
|
|
99
|
+
--color-popover-foreground: var(--popover-foreground);
|
|
100
|
+
--color-primary: var(--primary);
|
|
101
|
+
--color-primary-foreground: var(--primary-foreground);
|
|
102
|
+
--color-secondary: var(--secondary);
|
|
103
|
+
--color-secondary-foreground: var(--secondary-foreground);
|
|
104
|
+
--color-muted: var(--muted);
|
|
105
|
+
--color-muted-foreground: var(--muted-foreground);
|
|
106
|
+
--color-accent: var(--accent);
|
|
107
|
+
--color-accent-foreground: var(--accent-foreground);
|
|
108
|
+
--color-destructive: var(--destructive);
|
|
109
|
+
--color-border: var(--border);
|
|
110
|
+
--color-input: var(--input);
|
|
111
|
+
--color-ring: var(--ring);
|
|
112
|
+
--color-brand: var(--brand);
|
|
113
|
+
--color-brand-soft: var(--brand-soft);
|
|
114
|
+
--color-paper: var(--paper);
|
|
115
|
+
--color-chart-1: var(--chart-1);
|
|
116
|
+
--color-chart-2: var(--chart-2);
|
|
117
|
+
--color-chart-3: var(--chart-3);
|
|
118
|
+
--color-chart-4: var(--chart-4);
|
|
119
|
+
--color-chart-5: var(--chart-5);
|
|
120
|
+
--color-sidebar: var(--sidebar);
|
|
121
|
+
--color-sidebar-foreground: var(--sidebar-foreground);
|
|
122
|
+
--color-sidebar-primary: var(--sidebar-primary);
|
|
123
|
+
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
|
|
124
|
+
--color-sidebar-accent: var(--sidebar-accent);
|
|
125
|
+
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
|
|
126
|
+
--color-sidebar-border: var(--sidebar-border);
|
|
127
|
+
--color-sidebar-ring: var(--sidebar-ring);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
@layer base {
|
|
131
|
+
*,
|
|
132
|
+
::after,
|
|
133
|
+
::before,
|
|
134
|
+
::backdrop,
|
|
135
|
+
::file-selector-button {
|
|
136
|
+
border-color: var(--color-border, currentColor);
|
|
137
|
+
outline-color: var(--color-ring);
|
|
138
|
+
}
|
|
139
|
+
body {
|
|
140
|
+
background-color: var(--color-background);
|
|
141
|
+
color: var(--color-foreground);
|
|
142
|
+
font-family: Inter, ui-sans-serif, system-ui, -apple-system, sans-serif;
|
|
143
|
+
-webkit-font-smoothing: antialiased;
|
|
144
|
+
}
|
|
145
|
+
button {
|
|
146
|
+
cursor: pointer;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { Link, type PageAuth } from "@pylonsync/react";
|
|
3
|
+
import { siteConfig } from "@/lib/site.config";
|
|
4
|
+
|
|
5
|
+
// App shell: a slim top bar over a full-height chat. `auth.user_id` is resolved
|
|
6
|
+
// server-side from the session cookie before any HTML is sent, so the bar shows
|
|
7
|
+
// the account / "Sign in" with no flash. The chat page fills the rest of the
|
|
8
|
+
// viewport (h-[calc(100vh-3.5rem)] in chat-client.tsx — keep the header at h-14).
|
|
9
|
+
interface LayoutProps {
|
|
10
|
+
children: React.ReactNode;
|
|
11
|
+
url: string;
|
|
12
|
+
auth: PageAuth;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export default function RootLayout({ children, url, auth }: LayoutProps) {
|
|
16
|
+
// Resolved server-side from the session cookie. The app requires sign-in, so
|
|
17
|
+
// this is set on every in-app page; the header reflects it with no flash.
|
|
18
|
+
const signedIn = Boolean(auth?.user_id);
|
|
19
|
+
const { brand, colors } = siteConfig;
|
|
20
|
+
|
|
21
|
+
// The auth screen brings its own chrome → render it bare.
|
|
22
|
+
const path = (url ?? "").split("?")[0];
|
|
23
|
+
const isBare = path === "/login" || path.startsWith("/login/");
|
|
24
|
+
|
|
25
|
+
return (
|
|
26
|
+
<html
|
|
27
|
+
lang="en"
|
|
28
|
+
style={
|
|
29
|
+
{
|
|
30
|
+
"--brand": colors.brand,
|
|
31
|
+
"--brand-soft": colors.brandSoft,
|
|
32
|
+
"--paper": colors.paper,
|
|
33
|
+
} as React.CSSProperties
|
|
34
|
+
}
|
|
35
|
+
>
|
|
36
|
+
<head>
|
|
37
|
+
<meta charSet="utf-8" />
|
|
38
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
39
|
+
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
40
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossOrigin="anonymous" />
|
|
41
|
+
<link
|
|
42
|
+
rel="stylesheet"
|
|
43
|
+
href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap"
|
|
44
|
+
/>
|
|
45
|
+
</head>
|
|
46
|
+
<body className="bg-background text-foreground antialiased">
|
|
47
|
+
{isBare ? (
|
|
48
|
+
children
|
|
49
|
+
) : (
|
|
50
|
+
<>
|
|
51
|
+
<header className="flex h-14 items-center justify-between border-b border-zinc-200 bg-white px-4">
|
|
52
|
+
<Link href="/" className="flex items-center gap-2">
|
|
53
|
+
<span className="flex size-6 items-center justify-center rounded-[7px] bg-brand text-[13px] font-bold text-white">
|
|
54
|
+
{brand.letter}
|
|
55
|
+
</span>
|
|
56
|
+
<span className="text-[15px] font-semibold tracking-tight text-zinc-900">{brand.name}</span>
|
|
57
|
+
</Link>
|
|
58
|
+
{signedIn ? (
|
|
59
|
+
<span className="text-[13px] text-zinc-400">Signed in</span>
|
|
60
|
+
) : (
|
|
61
|
+
<Link
|
|
62
|
+
href="/login"
|
|
63
|
+
className="rounded-full border border-zinc-300 px-3.5 py-1.5 text-[13px] font-medium text-zinc-700 transition-colors hover:bg-zinc-50"
|
|
64
|
+
>
|
|
65
|
+
Sign in
|
|
66
|
+
</Link>
|
|
67
|
+
)}
|
|
68
|
+
</header>
|
|
69
|
+
{children}
|
|
70
|
+
</>
|
|
71
|
+
)}
|
|
72
|
+
</body>
|
|
73
|
+
</html>
|
|
74
|
+
);
|
|
75
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { Link, type Metadata, type PageProps } from "@pylonsync/react";
|
|
3
|
+
import { AuthForm } from "../auth-form";
|
|
4
|
+
import { siteConfig } from "@/lib/site.config";
|
|
5
|
+
|
|
6
|
+
export const metadata: Metadata = {
|
|
7
|
+
title: `Sign in — ${siteConfig.brand.name}`,
|
|
8
|
+
robots: "noindex",
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
// `app/login/page.tsx` → `/login`. Sign-in is required to use the chat (chats
|
|
12
|
+
// are tied to your account). Rendered bare. Already signed in? Skip back to the
|
|
13
|
+
// chat — `response.redirect` in the synchronous shell render is a real 307
|
|
14
|
+
// before any HTML is sent.
|
|
15
|
+
export default function LoginPage({ auth, response }: PageProps) {
|
|
16
|
+
// Already signed in (a real account, not an anonymous guest)? Skip the form.
|
|
17
|
+
if (auth.user_id && !auth.user_id.startsWith("guest_")) response.redirect("/");
|
|
18
|
+
const { brand } = siteConfig;
|
|
19
|
+
return (
|
|
20
|
+
<div className="flex min-h-screen items-center justify-center bg-white px-6 py-12">
|
|
21
|
+
<div className="w-full max-w-[400px] rounded-2xl border border-zinc-200/70 p-8">
|
|
22
|
+
<Link href="/" className="inline-flex">
|
|
23
|
+
<span className="flex size-9 items-center justify-center rounded-xl bg-zinc-900 text-base font-bold text-white">
|
|
24
|
+
{brand.letter}
|
|
25
|
+
</span>
|
|
26
|
+
</Link>
|
|
27
|
+
<h1 className="mt-5 text-[22px] font-semibold tracking-tight text-zinc-900">
|
|
28
|
+
Sign in to {brand.name}
|
|
29
|
+
</h1>
|
|
30
|
+
<p className="mt-1 text-[13px] text-zinc-500">
|
|
31
|
+
Create an account or sign in to start chatting.
|
|
32
|
+
</p>
|
|
33
|
+
<div className="mt-6">
|
|
34
|
+
<AuthForm />
|
|
35
|
+
</div>
|
|
36
|
+
</div>
|
|
37
|
+
</div>
|
|
38
|
+
);
|
|
39
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { Link, type NotFoundProps } from "@pylonsync/react";
|
|
3
|
+
|
|
4
|
+
// `app/not-found.tsx` → rendered at HTTP 404 for any unmatched URL (and when a
|
|
5
|
+
// page calls `response.notFound()`). Hydrated, so the link is a client nav.
|
|
6
|
+
export default function NotFound(_props: NotFoundProps) {
|
|
7
|
+
return (
|
|
8
|
+
<div className="mx-auto flex min-h-[60vh] max-w-3xl flex-col items-center justify-center px-6 text-center">
|
|
9
|
+
<h1 className="text-3xl font-semibold tracking-tight">404</h1>
|
|
10
|
+
<p className="mt-2 text-zinc-500">We couldn't find that page.</p>
|
|
11
|
+
<Link
|
|
12
|
+
href="/"
|
|
13
|
+
className="mt-6 inline-flex h-10 items-center rounded-full bg-zinc-900 px-5 text-sm font-medium text-white transition-colors hover:bg-zinc-700"
|
|
14
|
+
>
|
|
15
|
+
Back home
|
|
16
|
+
</Link>
|
|
17
|
+
</div>
|
|
18
|
+
);
|
|
19
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
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
|
+
|
|
6
|
+
export const metadata: Metadata = {
|
|
7
|
+
title: siteConfig.seo.title,
|
|
8
|
+
description: siteConfig.seo.description,
|
|
9
|
+
openGraph: { title: siteConfig.seo.title, description: siteConfig.seo.description, type: "website" },
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
// `app/page.tsx` → `/`. The whole app is the chat island — a sidebar of
|
|
13
|
+
// conversations + the streaming thread. Sign-in is REQUIRED: chats are tied to
|
|
14
|
+
// your account, so an unauthenticated visitor is redirected to /login (a real
|
|
15
|
+
// 307 from the synchronous shell render, before any HTML is sent).
|
|
16
|
+
export default function Home({ auth, response }: PageProps) {
|
|
17
|
+
// Real account required — reject anonymous + guest (guest_…) sessions.
|
|
18
|
+
if (!auth.user_id || auth.user_id.startsWith("guest_")) {
|
|
19
|
+
response.redirect("/login");
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
return <ChatApp />;
|
|
23
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import {
|
|
2
|
+
entity,
|
|
3
|
+
field,
|
|
4
|
+
policy,
|
|
5
|
+
auth,
|
|
6
|
+
buildManifest,
|
|
7
|
+
discoverAppRoutes,
|
|
8
|
+
} from "@pylonsync/sdk";
|
|
9
|
+
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
// ai-chat — a streaming AI chat app. Tokens stream from the built-in
|
|
12
|
+
// `POST /api/ai/stream` endpoint (your PYLON_AI_API_KEY never leaves the
|
|
13
|
+
// server); the conversation itself is sync-backed, so your chats follow you
|
|
14
|
+
// across tabs and devices in realtime — open two tabs and a reply you send in
|
|
15
|
+
// one shows up in the other as it's saved.
|
|
16
|
+
//
|
|
17
|
+
// Two data entities (+ User):
|
|
18
|
+
// • Conversation — a chat thread. Owner-scoped: you only ever see your own.
|
|
19
|
+
// • Message — one turn (user or assistant). Owner-scoped too. Both use
|
|
20
|
+
// `field.owner()` on userId, so the client can insert with a
|
|
21
|
+
// plain optimistic `db.insert` and the id is stamped from the
|
|
22
|
+
// session — unspoofable, no server function needed to write.
|
|
23
|
+
// • User — the account (email/password is built in).
|
|
24
|
+
//
|
|
25
|
+
// There's no single "owner"/admin — it's multi-user: every signed-in (or guest)
|
|
26
|
+
// visitor gets their own private chats.
|
|
27
|
+
// ---------------------------------------------------------------------------
|
|
28
|
+
|
|
29
|
+
const Conversation = entity(
|
|
30
|
+
"Conversation",
|
|
31
|
+
{
|
|
32
|
+
userId: field.string().owner(),
|
|
33
|
+
title: field.string().default("New chat"),
|
|
34
|
+
createdAt: field.datetime().defaultNow(),
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
indexes: [
|
|
38
|
+
{ name: "by_user", fields: ["userId"], unique: false },
|
|
39
|
+
{ name: "by_created", fields: ["createdAt"], unique: false },
|
|
40
|
+
],
|
|
41
|
+
},
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
const Message = entity(
|
|
45
|
+
"Message",
|
|
46
|
+
{
|
|
47
|
+
conversationId: field.string(),
|
|
48
|
+
userId: field.string().owner(),
|
|
49
|
+
role: field.string(), // "user" | "assistant"
|
|
50
|
+
content: field.string(),
|
|
51
|
+
createdAt: field.datetime().defaultNow(),
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
indexes: [
|
|
55
|
+
{ name: "by_conversation", fields: ["conversationId"], unique: false },
|
|
56
|
+
{ name: "by_user", fields: ["userId"], unique: false },
|
|
57
|
+
],
|
|
58
|
+
},
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
const User = entity(
|
|
62
|
+
"User",
|
|
63
|
+
{
|
|
64
|
+
email: field.string(),
|
|
65
|
+
displayName: field.string().optional(),
|
|
66
|
+
passwordHash: field.string().serverOnly().optional(),
|
|
67
|
+
avatarColor: field.string().optional(),
|
|
68
|
+
emailVerified: field.datetime().optional(),
|
|
69
|
+
createdAt: field.datetime().defaultNow(),
|
|
70
|
+
},
|
|
71
|
+
{ indexes: [{ name: "by_email", fields: ["email"], unique: true }] },
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
// Conversations + messages are PRIVATE: a signed-in (or guest) user can only
|
|
75
|
+
// read, create, and modify their OWN rows. `field.owner()` stamps userId from
|
|
76
|
+
// the session on insert, so "create your own" is enforced at write time and
|
|
77
|
+
// reads are scoped by the same id — your chats never leak to anyone else, and
|
|
78
|
+
// the sync engine only ever ships you yours.
|
|
79
|
+
const conversationPolicy = policy({
|
|
80
|
+
name: "conversation_owner",
|
|
81
|
+
entity: "Conversation",
|
|
82
|
+
allowRead: "auth.userId == data.userId",
|
|
83
|
+
allowInsert: "auth.userId != null",
|
|
84
|
+
allowUpdate: "auth.userId == data.userId",
|
|
85
|
+
allowDelete: "auth.userId == data.userId",
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
const messagePolicy = policy({
|
|
89
|
+
name: "message_owner",
|
|
90
|
+
entity: "Message",
|
|
91
|
+
allowRead: "auth.userId == data.userId",
|
|
92
|
+
allowInsert: "auth.userId != null",
|
|
93
|
+
allowUpdate: "auth.userId == data.userId",
|
|
94
|
+
allowDelete: "auth.userId == data.userId",
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
const userPolicy = policy({
|
|
98
|
+
name: "user_self",
|
|
99
|
+
entity: "User",
|
|
100
|
+
allowRead: "auth.userId == data.id",
|
|
101
|
+
allowInsert: "false",
|
|
102
|
+
allowUpdate: "false",
|
|
103
|
+
allowDelete: "false",
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
const manifest = buildManifest({
|
|
107
|
+
name: "__APP_NAME__",
|
|
108
|
+
version: "0.1.0",
|
|
109
|
+
entities: [Conversation, Message, User],
|
|
110
|
+
// No custom functions needed: messages are written with optimistic db.insert
|
|
111
|
+
// (owner-stamped), and streaming uses the built-in POST /api/ai/stream.
|
|
112
|
+
queries: [],
|
|
113
|
+
actions: [],
|
|
114
|
+
policies: [conversationPolicy, messagePolicy, userPolicy],
|
|
115
|
+
auth: auth(),
|
|
116
|
+
routes: await discoverAppRoutes(),
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
console.log(JSON.stringify(manifest, null, 2));
|
|
120
|
+
|
|
121
|
+
export default manifest;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://ui.shadcn.com/schema.json",
|
|
3
|
+
"style": "new-york",
|
|
4
|
+
"rsc": false,
|
|
5
|
+
"tsx": true,
|
|
6
|
+
"tailwind": {
|
|
7
|
+
"config": "",
|
|
8
|
+
"css": "app/globals.css",
|
|
9
|
+
"baseColor": "zinc",
|
|
10
|
+
"cssVariables": true
|
|
11
|
+
},
|
|
12
|
+
"aliases": {
|
|
13
|
+
"components": "@/components",
|
|
14
|
+
"utils": "@/lib/utils",
|
|
15
|
+
"ui": "@/components/ui",
|
|
16
|
+
"lib": "@/lib",
|
|
17
|
+
"hooks": "@/hooks"
|
|
18
|
+
},
|
|
19
|
+
"iconLibrary": "lucide"
|
|
20
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { mutation, v } from "@pylonsync/functions";
|
|
2
|
+
|
|
3
|
+
// deleteConversation — remove a conversation AND all its messages in one
|
|
4
|
+
// transaction. The client could delete the Conversation row itself (it's
|
|
5
|
+
// owner-scoped), but that would orphan the messages; this cascades. Gated to the
|
|
6
|
+
// owner: it verifies the conversation belongs to the caller before deleting.
|
|
7
|
+
export default mutation<{ conversationId: string }, { ok: boolean; deleted: number }>({
|
|
8
|
+
auth: "user",
|
|
9
|
+
args: { conversationId: v.id("Conversation") },
|
|
10
|
+
async handler(ctx, args) {
|
|
11
|
+
const convo = (await ctx.db.get("Conversation", args.conversationId)) as
|
|
12
|
+
| { userId: string }
|
|
13
|
+
| null;
|
|
14
|
+
if (!convo) return { ok: true, deleted: 0 };
|
|
15
|
+
if (convo.userId !== ctx.auth.userId) {
|
|
16
|
+
throw ctx.error("POLICY_DENIED", "You can only delete your own conversations.");
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const messages = (await ctx.db.unsafe.list("Message")) as unknown as {
|
|
20
|
+
id: string;
|
|
21
|
+
conversationId: string;
|
|
22
|
+
}[];
|
|
23
|
+
let deleted = 0;
|
|
24
|
+
for (const m of messages) {
|
|
25
|
+
if (m.conversationId === args.conversationId) {
|
|
26
|
+
await ctx.db.unsafe.delete("Message", m.id);
|
|
27
|
+
deleted++;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
await ctx.db.unsafe.delete("Conversation", args.conversationId);
|
|
31
|
+
return { ok: true, deleted };
|
|
32
|
+
},
|
|
33
|
+
});
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
// THE single source of truth for everything brand-specific on this AI chat app.
|
|
2
|
+
// Rebrand the whole thing by editing this ONE file — the layout + chat UI read
|
|
3
|
+
// from here. The create-pylon scaffolder and Mast target this file.
|
|
4
|
+
//
|
|
5
|
+
// Colors live here (applied as CSS variables on <html> in app/layout.tsx).
|
|
6
|
+
// Fictional demo copy — replace the values, keep the shape.
|
|
7
|
+
|
|
8
|
+
/* ----------------------------- types ----------------------------- */
|
|
9
|
+
|
|
10
|
+
export type Social = { label: string; href: string; path: string };
|
|
11
|
+
|
|
12
|
+
export type BaseConfig = {
|
|
13
|
+
brand: {
|
|
14
|
+
name: string;
|
|
15
|
+
letter: string;
|
|
16
|
+
domain: string;
|
|
17
|
+
email: string;
|
|
18
|
+
footerBlurb: string;
|
|
19
|
+
copyrightName: string;
|
|
20
|
+
socials: Social[];
|
|
21
|
+
};
|
|
22
|
+
colors: { brand: string; brandSoft: string; paper: string };
|
|
23
|
+
seo: { title: string; description: string };
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
// A selectable model. `id` is what's sent to /api/ai/stream; `provider` is just
|
|
27
|
+
// a label for the picker. Which models are actually accepted is gated server-side
|
|
28
|
+
// by PYLON_AI_MODELS_ALLOWED (+ the configured provider / gateway) — see README.
|
|
29
|
+
export type ChatModel = { id: string; label: string; provider: string };
|
|
30
|
+
|
|
31
|
+
export type ChatConfig = BaseConfig & {
|
|
32
|
+
chat: {
|
|
33
|
+
assistantName: string;
|
|
34
|
+
// Prepended as a system message on every request — sets the assistant's
|
|
35
|
+
// persona. Edit to retune the assistant without touching code.
|
|
36
|
+
systemPrompt: string;
|
|
37
|
+
emptyHeadline: string;
|
|
38
|
+
emptySubcopy: string;
|
|
39
|
+
inputPlaceholder: string;
|
|
40
|
+
// Starter prompts shown on the empty state; clicking one sends it.
|
|
41
|
+
suggestions: string[];
|
|
42
|
+
// The model picker. Curate to your setup: a single provider's models, or —
|
|
43
|
+
// with an OpenRouter-style gateway (PYLON_AI_PROVIDER=custom) — models across
|
|
44
|
+
// providers in one list. The selected id is sent as `model` per request.
|
|
45
|
+
models: ChatModel[];
|
|
46
|
+
defaultModel: string;
|
|
47
|
+
};
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
/* ----------------------------- config ---------------------------- */
|
|
51
|
+
|
|
52
|
+
export const siteConfig: ChatConfig = {
|
|
53
|
+
brand: {
|
|
54
|
+
name: "Lumen",
|
|
55
|
+
letter: "L",
|
|
56
|
+
domain: "lumen.chat",
|
|
57
|
+
email: "hello@lumen.example",
|
|
58
|
+
footerBlurb: "A fast, streaming AI assistant built on Pylon.",
|
|
59
|
+
copyrightName: "Lumen",
|
|
60
|
+
socials: [
|
|
61
|
+
{
|
|
62
|
+
label: "X",
|
|
63
|
+
href: "https://x.com",
|
|
64
|
+
path: "M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z",
|
|
65
|
+
},
|
|
66
|
+
],
|
|
67
|
+
},
|
|
68
|
+
|
|
69
|
+
colors: { brand: "#6d28d9", brandSoft: "#ede9fe", paper: "#faf9fc" },
|
|
70
|
+
|
|
71
|
+
seo: {
|
|
72
|
+
title: "Lumen — a fast streaming AI assistant.",
|
|
73
|
+
description:
|
|
74
|
+
"A streaming AI chat app built on Pylon. Conversations sync across your tabs and devices in realtime; your API key never leaves the server.",
|
|
75
|
+
},
|
|
76
|
+
|
|
77
|
+
chat: {
|
|
78
|
+
assistantName: "Lumen",
|
|
79
|
+
systemPrompt:
|
|
80
|
+
"You are Lumen, a friendly, concise AI assistant. Answer clearly and get to the point. Use Markdown when it helps.",
|
|
81
|
+
emptyHeadline: "How can I help?",
|
|
82
|
+
emptySubcopy: "Ask anything. Your conversations are saved and stay in sync across your tabs.",
|
|
83
|
+
inputPlaceholder: "Message Lumen…",
|
|
84
|
+
suggestions: [
|
|
85
|
+
"Explain WebSockets like I'm five.",
|
|
86
|
+
"Draft a friendly out-of-office reply.",
|
|
87
|
+
"Give me 5 dinner ideas using chicken and rice.",
|
|
88
|
+
"What's the difference between SSR and SSG?",
|
|
89
|
+
],
|
|
90
|
+
// Current Claude models (verify the ids for your provider before shipping —
|
|
91
|
+
// model names move fast). Whichever you keep here must be in
|
|
92
|
+
// PYLON_AI_MODELS_ALLOWED for switching to work (see .env.example). To offer
|
|
93
|
+
// models from OTHER providers (OpenAI, Google, …) in the same list, route
|
|
94
|
+
// through an OpenRouter-style gateway (PYLON_AI_PROVIDER=custom +
|
|
95
|
+
// PYLON_AI_BASE_URL) and use that gateway's slugs here.
|
|
96
|
+
models: [
|
|
97
|
+
{ id: "claude-sonnet-4-6", label: "Claude Sonnet 4.6", provider: "Anthropic" },
|
|
98
|
+
{ id: "claude-opus-4-8", label: "Claude Opus 4.8", provider: "Anthropic" },
|
|
99
|
+
{ id: "claude-haiku-4-5", label: "Claude Haiku 4.5", provider: "Anthropic" },
|
|
100
|
+
],
|
|
101
|
+
defaultModel: "claude-sonnet-4-6",
|
|
102
|
+
},
|
|
103
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type ClassValue, clsx } from "clsx";
|
|
2
|
+
import { twMerge } from "tailwind-merge";
|
|
3
|
+
|
|
4
|
+
// `cn` — the shadcn class merger. clsx resolves conditional/array class
|
|
5
|
+
// inputs; tailwind-merge then dedupes conflicting Tailwind utilities so
|
|
6
|
+
// the last one wins (e.g. `cn("px-2", "px-4")` → "px-4"). Every shadcn
|
|
7
|
+
// component routes its className through this.
|
|
8
|
+
export function cn(...inputs: ClassValue[]) {
|
|
9
|
+
return twMerge(clsx(inputs));
|
|
10
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "__APP_NAME_KEBAB__",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"dev": "pylon dev",
|
|
8
|
+
"deploy": "pylon deploy",
|
|
9
|
+
"check": "tsc --noEmit"
|
|
10
|
+
},
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"@pylonsync/react": "^__PYLON_VERSION__",
|
|
13
|
+
"@pylonsync/sdk": "^__PYLON_VERSION__",
|
|
14
|
+
"@pylonsync/functions": "^__PYLON_VERSION__",
|
|
15
|
+
"@pylonsync/client": "^__PYLON_VERSION__",
|
|
16
|
+
"react": "^19.0.0",
|
|
17
|
+
"react-dom": "^19.0.0",
|
|
18
|
+
"tailwindcss": "^4.3.0",
|
|
19
|
+
"@tailwindcss/cli": "^4.3.0",
|
|
20
|
+
"tw-animate-css": "^1.2.0",
|
|
21
|
+
"class-variance-authority": "^0.7.1",
|
|
22
|
+
"clsx": "^2.1.1",
|
|
23
|
+
"tailwind-merge": "^2.5.0",
|
|
24
|
+
"lucide-react": "^0.460.0",
|
|
25
|
+
"@radix-ui/react-slot": "^1.1.0"
|
|
26
|
+
},
|
|
27
|
+
"devDependencies": {
|
|
28
|
+
"@pylonsync/cli": "^__PYLON_VERSION__",
|
|
29
|
+
"@types/node": "^22.0.0",
|
|
30
|
+
"@types/react": "^19.0.0",
|
|
31
|
+
"@types/react-dom": "^19.0.0",
|
|
32
|
+
"typescript": "^5.6.0"
|
|
33
|
+
}
|
|
34
|
+
}
|