-
Notifications
You must be signed in to change notification settings - Fork 0
Talocode rebrand and API-first foundation #37
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
Abdulmuiz44
wants to merge
19
commits into
main
Choose a base branch
from
codex/talocode-api-first-brand
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
19 commits
Select commit
Hold shift + click to select a range
cae5df8
Surface quality warnings and track warning analytics
6ad4876
Fix missing quality warning type for deploy build
14dcdda
Add structured quality report to generation panel
3369750
Rebrand LaunchPix for Talocode and API-first usage
Abdulmuiz44 b019673
Add Talocode and API-first env vars
Abdulmuiz44 73ccd1b
Rebrand metadata for Talocode LaunchPix
Abdulmuiz44 7709691
Rebrand homepage metadata for Talocode LaunchPix
Abdulmuiz44 c93a898
Shift app chrome to Talocode monochrome palette
Abdulmuiz44 9a5e8fc
Use Talocode brand asset in logo
Abdulmuiz44 cde22a9
Flatten shared buttons to monochrome Talocode styling
Abdulmuiz44 538e763
Add LaunchPix API key enforcement
Abdulmuiz44 1f67d08
Add API user id helper for developer endpoints
Abdulmuiz44 b14d844
Add API-first project endpoints
Abdulmuiz44 8b828d2
Add API documentation page
Abdulmuiz44 4e4f466
Add Talocode asset directory
Abdulmuiz44 430b560
Add Talocode logo asset
Abdulmuiz44 b6f457d
Add Talocode banner asset
Abdulmuiz44 d5b0191
Add Render deployment config
Abdulmuiz44 700614a
Add API-first generation endpoint
Abdulmuiz44 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,126 +1,74 @@ | ||
| # LaunchPix | ||
| # Talocode LaunchPix | ||
|
|
||
| LaunchPix is a Mistral-assisted asset generator for product launches. | ||
| It turns raw screenshots into polished listing visuals, promo tiles, and hero banners. | ||
| Talocode LaunchPix is an API-first, open-source launch visual engine. | ||
| It turns product screenshots into listing frames, promo tiles, and hero banners with deterministic fallback rendering. | ||
|
|
||
| ## Design system | ||
| - `DESIGN.md` is the canonical design brain for the product UI. | ||
| - `docs/design-md/google-designmd-spec.md` is a local copy of the Google DESIGN.md specification. | ||
| - `docs/design-md/README.md` explains how to use both files in this repo. | ||
| ## Repository | ||
| - Canonical repo: `https://github.com/talocode/launchpix` | ||
|
|
||
| ## Tech stack | ||
| - Next.js App Router + TypeScript | ||
| - Tailwind CSS + reusable UI primitives | ||
| - Supabase (Auth, Postgres, Storage) | ||
| - Mistral structured planning and image generation | ||
| - Deterministic SVG -> PNG fallback rendering (`@resvg/resvg-js`) | ||
| - Lemon Squeezy credit-pack billing and webhook fulfillment | ||
|
|
||
| ## Core product flow | ||
| 1. Sign in | ||
| 2. Create project and upload screenshots | ||
| 3. Generate structured asset plan via Mistral | ||
| 4. Generate image assets through a Mistral image-generation agent | ||
| 5. Preview/download assets while credits remain | ||
| ## Product direction | ||
| - API first: developer endpoints live under `/api/v1/*`. | ||
| - Open source core: code is public, but API usage requires `LAUNCHPIX_API_KEY`. | ||
| - Credits model: users start with free credits, then buy one-time credit packs. | ||
|
|
||
| ## Pricing model implemented | ||
| - Every account starts with 300 credits. | ||
| - Existing accounts are raised to at least 300 credits by `0004_credit_balance_model.sql`. | ||
| - Billing is credit based, not subscription based. | ||
| - Users buy one-time Lemon Squeezy credit packs after exhausting their included credits. | ||
|
|
||
| ## Required environment variables | ||
| ## Core stack | ||
| - Next.js App Router + TypeScript | ||
| - Supabase (Postgres, Storage) | ||
| - Mistral (planning + image generation) | ||
| - Lemon Squeezy (credit-pack checkout + webhook fulfillment) | ||
| - Resend (transactional email) | ||
|
|
||
| ## API authentication | ||
| Every `/api/v1/*` request must include: | ||
| - `x-launchpix-api-key: <LAUNCHPIX_API_KEY>` | ||
| - `x-launchpix-user-id: <owner-user-uuid>` | ||
|
|
||
| Supported alternatives: | ||
| - `x-api-key` | ||
| - `Authorization: Bearer <LAUNCHPIX_API_KEY>` | ||
|
|
||
| ## Developer API endpoints | ||
| - `GET /api/v1/projects` | ||
| - `POST /api/v1/projects` | ||
| - `GET /api/v1/projects/:projectId/generate` | ||
| - `POST /api/v1/projects/:projectId/generate` | ||
|
|
||
| ## Environment variables | ||
| See `.env.example`. | ||
| Minimum required: | ||
|
|
||
| Critical keys: | ||
| - `NEXT_PUBLIC_APP_URL` | ||
| - `NEXT_PUBLIC_SUPABASE_URL` | ||
| - `NEXT_PUBLIC_SUPABASE_ANON_KEY` | ||
| - `SUPABASE_SERVICE_ROLE_KEY` | ||
| - `DATABASE_URL` | ||
| - `NEXTAUTH_SECRET` | ||
| - `GOOGLE_CLIENT_ID` | ||
| - `GOOGLE_CLIENT_SECRET` | ||
| - `MISTRAL_API_KEY` | ||
| - `MISTRAL_MODEL_VISION` | ||
| - `MISTRAL_MODEL_TEXT` | ||
| - `MISTRAL_MODEL_VISION` | ||
| - `MISTRAL_IMAGE_MODEL` | ||
| - `MISTRAL_IMAGE_AGENT_ID` (optional) | ||
| - `LEMON_SQUEEZY_API_KEY` | ||
| - `LEMON_SQUEEZY_STORE_ID` | ||
| - `LEMON_SQUEEZY_WEBHOOK_SECRET` | ||
| - `LEMON_SQUEEZY_STARTER_CREDITS_VARIANT_ID` | ||
| - `LEMON_SQUEEZY_CREATOR_CREDITS_VARIANT_ID` | ||
| - `LEMON_SQUEEZY_STUDIO_CREDITS_VARIANT_ID` | ||
|
|
||
| Optional for Supabase CLI workflows: | ||
| - `SUPABASE_ACCESS_TOKEN` | ||
| - `SUPABASE_DB_PASSWORD` | ||
| - `LAUNCHPIX_API_KEY` | ||
| - `LEMON_SQUEEZY_*` | ||
| - `RESEND_API_KEY` | ||
|
|
||
| ## Local setup | ||
| 1. Copy env: | ||
| - `cp .env.example .env.local` | ||
| 2. Install dependencies: | ||
| - `npm install` | ||
| 4. Apply database migrations using one of these options: | ||
| - Supabase CLI: `npx supabase db push --linked` | ||
| - or run the SQL files in `supabase/migrations/` in order | ||
| 5. Start dev server: | ||
| - `npm run dev` | ||
| 1. Copy env file: `cp .env.example .env.local` | ||
| 2. Install: `npm install` | ||
| 3. Apply DB migrations: `npx supabase db push --linked` | ||
| 4. Start app: `npm run dev` | ||
|
|
||
| Recommended validation commands: | ||
| Validation: | ||
| - `npm run typecheck` | ||
| - `npm run build` | ||
|
|
||
| ## Supabase notes | ||
| - Enable email auth. | ||
| - Ensure storage buckets exist: | ||
| - `project-uploads-raw` | ||
| - `project-uploads-normalized` | ||
| - `launchpix-assets` | ||
| - Apply RLS policies from migrations. | ||
| - If you use the Supabase CLI, link the project first with `npx supabase link`. | ||
|
|
||
| ## Mistral notes | ||
| - Mistral is used for structured product/copy/layout planning. | ||
| - Final image assets are generated through a Mistral Agent with the built-in `image_generation` tool. | ||
| - Planning default model: `mistral-small-2506` (configurable via env). | ||
| - Image generation default model: `mistral-medium-latest` (configurable via `MISTRAL_IMAGE_MODEL`). | ||
| - `MISTRAL_IMAGE_AGENT_ID` can point to a pre-created image-generation agent. If it is omitted, LaunchPix creates an agent at runtime. | ||
| - If Mistral image generation fails, LaunchPix falls back to deterministic SVG -> PNG rendering so generation does not hard-fail. | ||
|
|
||
| ## Lemon Squeezy notes | ||
| - Checkout init: `POST /api/billing/checkout` | ||
| - Verification: purchases are fulfilled by webhook after Lemon Squeezy confirms the order | ||
| - Webhook: `POST /api/billing/webhook` | ||
| - Configure Lemon Squeezy webhook URL to point to `/api/billing/webhook`. | ||
| - Select the `order_created` event for credit fulfillment. | ||
| - Create three Lemon Squeezy variants and map them to the variant ID env vars in `.env.example`. | ||
|
|
||
| ## Commands | ||
| - `npm run dev` | ||
| - `npm run lint` | ||
| - `npm run typecheck` | ||
| - `npm run build` | ||
| - `npm run video:studio` | ||
| - `npm run video:render` | ||
| - `npm run video:render:chrome` on Windows if Remotion cannot download Chrome Headless Shell | ||
|
|
||
| ## Demo video | ||
| - The Remotion demo composition lives in `remotion/`. | ||
| - Rendered output is written to `output/launchpix-demo.mp4`. | ||
| - The video explains the core LaunchPix story: project brief, screenshot uploads, Mistral planning, image generation, fallback rendering, exports, credits, and billing. | ||
|
|
||
| ## Deployment notes | ||
| - Set all env vars in hosting provider. | ||
| - `NEXT_PUBLIC_APP_URL` must be set in the hosting provider's production environment to your live domain; `.env.local` is only used locally. | ||
| - Use HTTPS and production callback URLs for Lemon Squeezy. | ||
| - Auth confirmation and billing redirects are built from `NEXT_PUBLIC_APP_URL`, so production must not point this to localhost. | ||
| - Keep `package-lock.json` committed so CI and hosting builds install the same dependency tree. | ||
| - Confirm webhook signature secret matches deployment env. | ||
|
|
||
| ## Netlify notes | ||
| - Build command: `npm run build` | ||
| - Install command: `npm install` or `npm ci` | ||
| - The app relies on `@resvg/resvg-js` during server rendering, so the current `next.config.ts` must be preserved in deployments. | ||
| ## Render deployment | ||
| - Render config is in [`render.yaml`](/C:/Users/Hp/Documents/Github/LaunchPix/render.yaml). | ||
| - Build command: `npm ci && npm run build` | ||
| - Start command: `npm run start` | ||
| - Set all required env vars in Render dashboard. | ||
|
|
||
| ## Known MVP constraints | ||
| - Rate limiting is lightweight (in-memory). | ||
| - Credit packs are one-time purchases; subscription renewal automation is intentionally not used. | ||
| - Visual templates are production-capable baseline and can be expanded further. | ||
| ## Legacy note | ||
| Previous Netlify-specific deployment notes were removed in favor of Render as the primary target. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,37 @@ | ||
| import { NextResponse } from "next/server"; | ||
| import { requireLaunchPixApiKey } from "@/lib/api-key"; | ||
| import { requireApiUserId } from "@/lib/api-user"; | ||
| import { getProjectOverview } from "@/lib/services/projects/queries"; | ||
| import { runGenerationForProject } from "@/lib/services/generations/runner"; | ||
| import { getLatestGeneration } from "@/lib/services/generations/queries"; | ||
| import { allowGenerationAttempt } from "@/lib/services/access/rate-limit"; | ||
|
|
||
| export async function GET(request: Request, { params }: { params: Promise<{ projectId: string }> }) { | ||
| const unauthorized = requireLaunchPixApiKey(request); | ||
| if (unauthorized) return unauthorized; | ||
|
|
||
| const userResult = requireApiUserId(request); | ||
| if ("response" in userResult) return userResult.response; | ||
|
|
||
| const { projectId } = await params; | ||
| const { project } = await getProjectOverview(projectId, userResult.userId); | ||
| const generation = await getLatestGeneration(project.id); | ||
| return NextResponse.json({ generation }); | ||
| } | ||
|
|
||
| export async function POST(request: Request, { params }: { params: Promise<{ projectId: string }> }) { | ||
| const unauthorized = requireLaunchPixApiKey(request); | ||
| if (unauthorized) return unauthorized; | ||
|
|
||
| const userResult = requireApiUserId(request); | ||
| if ("response" in userResult) return userResult.response; | ||
|
|
||
| if (!allowGenerationAttempt(userResult.userId)) return NextResponse.json({ error: "Too many generation attempts. Please wait and retry." }, { status: 429 }); | ||
|
|
||
| const { projectId } = await params; | ||
| const { project, uploads } = await getProjectOverview(projectId, userResult.userId); | ||
| if (!uploads.length) return NextResponse.json({ error: "At least one screenshot is required." }, { status: 400 }); | ||
|
|
||
| const { generationId } = await runGenerationForProject(project, uploads); | ||
| return NextResponse.json({ generationId }, { status: 201 }); | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,62 @@ | ||
| import { NextResponse } from "next/server"; | ||
| import { requireLaunchPixApiKey } from "@/lib/api-key"; | ||
| import { requireApiUserId } from "@/lib/api-user"; | ||
| import { createProjectSchema } from "@/lib/validation/project"; | ||
| import { createSupabaseServerClient } from "@/lib/supabase/server"; | ||
|
|
||
| export async function GET(request: Request) { | ||
| const unauthorized = requireLaunchPixApiKey(request); | ||
| if (unauthorized) return unauthorized; | ||
|
|
||
| const userResult = requireApiUserId(request); | ||
| if ("response" in userResult) return userResult.response; | ||
|
|
||
| const supabase = await createSupabaseServerClient(); | ||
| const { data, error } = await supabase | ||
| .from("projects") | ||
| .select("id,name,product_type,platform,status,created_at,updated_at") | ||
| .eq("user_id", userResult.userId) | ||
| .order("updated_at", { ascending: false }); | ||
|
|
||
| if (error) return NextResponse.json({ error: error.message }, { status: 500 }); | ||
| return NextResponse.json({ projects: data ?? [] }); | ||
| } | ||
|
|
||
| export async function POST(request: Request) { | ||
| const unauthorized = requireLaunchPixApiKey(request); | ||
| if (unauthorized) return unauthorized; | ||
|
|
||
| const userResult = requireApiUserId(request); | ||
| if ("response" in userResult) return userResult.response; | ||
|
|
||
| const body = await request.json().catch(() => null); | ||
| const parsed = createProjectSchema.safeParse({ ...body, userId: userResult.userId }); | ||
| if (!parsed.success) { | ||
| return NextResponse.json({ error: parsed.error.issues[0]?.message ?? "Invalid payload." }, { status: 400 }); | ||
| } | ||
|
|
||
| const supabase = await createSupabaseServerClient(); | ||
| const payload = { | ||
| user_id: userResult.userId, | ||
| name: parsed.data.name, | ||
| product_type: parsed.data.productType, | ||
| platform: parsed.data.platform, | ||
| description: parsed.data.description, | ||
| audience: parsed.data.audience, | ||
| website_url: parsed.data.websiteUrl || null, | ||
| primary_color: parsed.data.primaryColor, | ||
| style_preset: parsed.data.stylePreset, | ||
| status: "ready" | ||
| }; | ||
|
|
||
| const { data: project, error } = await supabase.from("projects").insert(payload).select("*").single(); | ||
| if (error || !project) return NextResponse.json({ error: error?.message ?? "Unable to create project." }, { status: 500 }); | ||
|
|
||
| const { data: draft } = await supabase.from("generations").select("id").eq("project_id", project.id).eq("status", "draft").maybeSingle(); | ||
| if (!draft) { | ||
| const { error: generationError } = await supabase.from("generations").insert({ project_id: project.id, status: "draft" }); | ||
| if (generationError) return NextResponse.json({ error: generationError.message }, { status: 500 }); | ||
| } | ||
|
|
||
| return NextResponse.json({ project }, { status: 201 }); | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,44 @@ | ||
| import Link from "next/link"; | ||
| import { ArrowRight } from "lucide-react"; | ||
| import { TopNav } from "@/components/marketing/top-nav"; | ||
| import { MarketingFooter } from "@/components/marketing/footer"; | ||
| import { Button } from "@/components/ui/button"; | ||
|
|
||
| export default function ApiDocsPage() { | ||
| return ( | ||
| <> | ||
| <TopNav /> | ||
| <main className="app-shell py-14 sm:py-16"> | ||
| <section className="surface p-6 sm:p-10"> | ||
| <p className="eyebrow">API first</p> | ||
| <h1 className="mt-5 text-3xl font-semibold sm:text-4xl">Talocode LaunchPix Developer API</h1> | ||
| <p className="mt-4 max-w-3xl text-sm leading-7 text-slate-600 dark:text-slate-400"> | ||
| Talocode LaunchPix is open source and API-first. To use production endpoints, request `LAUNCHPIX_API_KEY` and pass it with each request. | ||
| </p> | ||
| <div className="mt-8 grid gap-4 sm:grid-cols-2"> | ||
| <div className="surface-muted p-5"> | ||
| <p className="text-sm font-semibold">Auth headers</p> | ||
| <p className="mt-2 text-sm text-slate-600 dark:text-slate-400">`x-launchpix-api-key: <your-key>`</p> | ||
| <p className="mt-1 text-sm text-slate-600 dark:text-slate-400">`x-launchpix-user-id: <owner-uuid>`</p> | ||
| </div> | ||
| <div className="surface-muted p-5"> | ||
| <p className="text-sm font-semibold">Endpoints</p> | ||
| <p className="mt-2 text-sm text-slate-600 dark:text-slate-400">`GET /api/v1/projects`</p> | ||
| <p className="mt-1 text-sm text-slate-600 dark:text-slate-400">`POST /api/v1/projects`</p> | ||
| <p className="mt-1 text-sm text-slate-600 dark:text-slate-400">`POST /api/v1/projects/:projectId/generate`</p> | ||
| </div> | ||
| </div> | ||
| <div className="mt-8"> | ||
| <Button asChild> | ||
| <Link href="/contact"> | ||
| Request API key | ||
| <ArrowRight className="size-4" /> | ||
| </Link> | ||
| </Button> | ||
| </div> | ||
| </section> | ||
| </main> | ||
| <MarketingFooter /> | ||
| </> | ||
| ); | ||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When an API client starts generation for a valid project but
runGenerationForProjectrejects—e.g.consumeGenerationCreditthrowsNo credits remaining...or quality/rendering fails—this new v1 route lets the exception escape. The existing/api/generations/[projectId]route catches the same errors and maps exhausted credits to a 402 JSON response, but/api/v1/projects/:projectId/generatewill return a generic 500/Next error response, making the documented API-first endpoint hard for clients to handle and hiding the billing action they need to take.Useful? React with 👍 / 👎.