From 62b002fd9cabae420588b1fa316a95c350246769 Mon Sep 17 00:00:00 2001 From: "Sameer .I. Siddiqui" Date: Mon, 12 Oct 2026 13:37:44 +0530 Subject: [PATCH 1/6] feat(solutions): typed solution registry, editorial assets and public discovery foundations --- apps/web/public/solution-art/businesses.svg | 1 + apps/web/public/solution-art/ecommerce.svg | 1 + apps/web/public/solution-art/explorer.svg | 1 + .../solution-art/marketing-agencies.svg | 1 + .../public/solution-art/production-teams.svg | 1 + .../solution-art/social-media-creators.svg | 1 + apps/web/src/app/page.tsx | 1 + apps/web/src/app/pricing/page.tsx | 1 + apps/web/src/app/sitemap.ts | 3 + .../marketing/tools/tools-shell.tsx | 2 + apps/web/src/lib/public-solutions.test.ts | 39 +++ apps/web/src/lib/public-solutions.ts | 228 ++++++++++++++++++ 12 files changed, 280 insertions(+) create mode 100644 apps/web/public/solution-art/businesses.svg create mode 100644 apps/web/public/solution-art/ecommerce.svg create mode 100644 apps/web/public/solution-art/explorer.svg create mode 100644 apps/web/public/solution-art/marketing-agencies.svg create mode 100644 apps/web/public/solution-art/production-teams.svg create mode 100644 apps/web/public/solution-art/social-media-creators.svg create mode 100644 apps/web/src/lib/public-solutions.test.ts create mode 100644 apps/web/src/lib/public-solutions.ts diff --git a/apps/web/public/solution-art/businesses.svg b/apps/web/public/solution-art/businesses.svg new file mode 100644 index 00000000..47ae6109 --- /dev/null +++ b/apps/web/public/solution-art/businesses.svg @@ -0,0 +1 @@ +Brand content illustrationOriginal editorial composition showing a campaign planning board, creative cards and connected output formats.BRAND / 04MESSAGECREATIVE OUTPUTVISUALIDEA → CREATE → REFINE \ No newline at end of file diff --git a/apps/web/public/solution-art/ecommerce.svg b/apps/web/public/solution-art/ecommerce.svg new file mode 100644 index 00000000..46e4052a --- /dev/null +++ b/apps/web/public/solution-art/ecommerce.svg @@ -0,0 +1 @@ +Retail creative illustrationOriginal editorial composition showing a campaign planning board, creative cards and connected output formats.PRODUCT / 02PRODUCTCREATIVE OUTPUT01CAMPAIGNIDEA → CREATE → REFINE \ No newline at end of file diff --git a/apps/web/public/solution-art/explorer.svg b/apps/web/public/solution-art/explorer.svg new file mode 100644 index 00000000..ea8f02c8 --- /dev/null +++ b/apps/web/public/solution-art/explorer.svg @@ -0,0 +1 @@ +Creative solutions illustrationOriginal editorial composition showing a campaign planning board, creative cards and connected output formats.ONE WORKSPACECONCEPTCREATIVE OUTPUTEVERY FORMATIDEA → CREATE → REFINE \ No newline at end of file diff --git a/apps/web/public/solution-art/marketing-agencies.svg b/apps/web/public/solution-art/marketing-agencies.svg new file mode 100644 index 00000000..4e5f1de8 --- /dev/null +++ b/apps/web/public/solution-art/marketing-agencies.svg @@ -0,0 +1 @@ +Agency campaign illustrationOriginal editorial composition showing a campaign planning board, creative cards and connected output formats.CAMPAIGN / 01BRIEFCREATIVE OUTPUTMOTIONIDEA → CREATE → REFINE \ No newline at end of file diff --git a/apps/web/public/solution-art/production-teams.svg b/apps/web/public/solution-art/production-teams.svg new file mode 100644 index 00000000..75d87da1 --- /dev/null +++ b/apps/web/public/solution-art/production-teams.svg @@ -0,0 +1 @@ +Production flow illustrationOriginal editorial composition showing a campaign planning board, creative cards and connected output formats.STUDIO / 05PLANCREATIVE OUTPUTDELIVERIDEA → CREATE → REFINE \ No newline at end of file diff --git a/apps/web/public/solution-art/social-media-creators.svg b/apps/web/public/solution-art/social-media-creators.svg new file mode 100644 index 00000000..5e1780eb --- /dev/null +++ b/apps/web/public/solution-art/social-media-creators.svg @@ -0,0 +1 @@ +Social stories illustrationOriginal editorial composition showing a campaign planning board, creative cards and connected output formats.SOCIAL / 03STORYCREATIVE OUTPUTFORMATIDEA → CREATE → REFINE \ No newline at end of file diff --git a/apps/web/src/app/page.tsx b/apps/web/src/app/page.tsx index 8e8361f2..0de33ed9 100644 --- a/apps/web/src/app/page.tsx +++ b/apps/web/src/app/page.tsx @@ -186,6 +186,7 @@ export default function HomePage() { > {[ ["/tools", "Explore Tools"], + ["/solutions", "Solutions"], ["#platform", "Platform"], ["#models", "Models"], ["#workflow", "Workflow"], diff --git a/apps/web/src/app/pricing/page.tsx b/apps/web/src/app/pricing/page.tsx index 59e9aabe..5793315b 100644 --- a/apps/web/src/app/pricing/page.tsx +++ b/apps/web/src/app/pricing/page.tsx @@ -81,6 +81,7 @@ export default async function PricingPage() { FAQ + Solutions Learn diff --git a/apps/web/src/app/sitemap.ts b/apps/web/src/app/sitemap.ts index ec9d3d82..9118e971 100644 --- a/apps/web/src/app/sitemap.ts +++ b/apps/web/src/app/sitemap.ts @@ -3,6 +3,7 @@ import { db } from "@aiwa/db"; import { publishedContent } from "@aiwa/learn"; import { learnOrigin } from "@/lib/learn/queries"; import { publicTools } from "@/lib/public-tools"; +import { solutions } from "@/lib/public-solutions"; import { getPublicPricingCatalogue } from "@/lib/public-pricing"; export const dynamic = "force-dynamic"; export default async function sitemap(): Promise { @@ -22,6 +23,8 @@ export default async function sitemap(): Promise { { url: origin }, { url: `${origin}/pricing` }, { url: `${origin}/tools` }, + { url: `${origin}/solutions` }, + ...solutions.map((solution) => ({ url: origin + "/solutions/" + solution.slug })), ...publicTools.map((tool) => ({ url: `${origin}/tools/${tool.slug}` })), ...operations.map((item) => ({ url: `${origin}/tools/operations/${encodeURIComponent(item.id)}`, diff --git a/apps/web/src/components/marketing/tools/tools-shell.tsx b/apps/web/src/components/marketing/tools/tools-shell.tsx index aef3c0f2..98845c93 100644 --- a/apps/web/src/components/marketing/tools/tools-shell.tsx +++ b/apps/web/src/components/marketing/tools/tools-shell.tsx @@ -21,6 +21,7 @@ export function ToolsHeader() { Explore tools + Solutions Pricing @@ -65,6 +66,7 @@ export function ToolsFooter() { > Home All tools + Solutions Pricing Learn Privacy diff --git a/apps/web/src/lib/public-solutions.test.ts b/apps/web/src/lib/public-solutions.test.ts new file mode 100644 index 00000000..fe23bb8f --- /dev/null +++ b/apps/web/src/lib/public-solutions.test.ts @@ -0,0 +1,39 @@ +import { describe, expect, it } from "vitest"; +import { publicToolById } from "./public-tools"; +import { recommendSolution, relevantModels, solutionBySlug, solutionHref, solutions } from "./public-solutions"; +import { pricingCategories } from "./public-pricing-contract"; +describe("public solutions", () => { + it("has five unique, safe routes and complete editorial content", () => { + expect(solutions).toHaveLength(5); + expect(new Set(solutions.map((s) => s.slug)).size).toBe(5); + for (const s of solutions) { + expect(s.slug).toMatch(/^[a-z]+(?:-[a-z]+)*$/); + expect(s.description.length).toBeGreaterThan(80); + expect(s.benefits.length).toBeGreaterThanOrEqual(3); + expect(s.workflow.length).toBeGreaterThanOrEqual(4); + expect(s.questions.length).toBeGreaterThanOrEqual(3); + expect(solutionBySlug(s.slug)).toBe(s); + expect(solutionHref(s)).toBe("/solutions/" + s.slug); + for (const step of s.workflow) expect(publicToolById(step.toolId)).toBeDefined(); + for (const category of s.modelCategories) expect(pricingCategories.some((p) => p.id === category)).toBe(true); + for (const slug of s.related) expect(solutionBySlug(slug)).toBeDefined(); + } + expect(solutionBySlug("../admin")).toBeUndefined(); + }); + it("provides deterministic recommendations from real tool IDs", () => { + const result = recommendSolution("ecommerce", "product", "cost"); + expect(result?.solution.slug).toBe("ecommerce"); + expect(result?.tools[0]?.id).toBe("image-studio"); + expect(result?.tools.every((tool) => Boolean(publicToolById(tool.id)))).toBe(true); + expect(result?.advice).toMatch(/quote/); + expect(recommendSolution("not-real" as "ecommerce", "social", "speed")).toBeNull(); + }); + it("filters the public-safe catalogue by category without altering prices", () => { + const rows = [ + { id: "a", category: "image", baseCredits: "123" }, + { id: "b", category: "chat", baseCredits: "456" }, + ] as Parameters[0]; + expect(relevantModels(rows, ["image"]).map((row) => row.id)).toEqual(["a"]); + expect(relevantModels(rows, ["image"])[0]?.baseCredits).toBe("123"); + }); +}); diff --git a/apps/web/src/lib/public-solutions.ts b/apps/web/src/lib/public-solutions.ts new file mode 100644 index 00000000..b0e72fc6 --- /dev/null +++ b/apps/web/src/lib/public-solutions.ts @@ -0,0 +1,228 @@ +import type { IconName } from "@/components/ui/icon"; +import { publicToolById } from "./public-tools"; +import type { PricingCategory, PublicPricingItem } from "./public-pricing-contract"; + +export type SolutionSlug = + | "marketing-agencies" + | "ecommerce" + | "social-media-creators" + | "businesses" + | "production-teams"; + +export type Solution = { + slug: SolutionSlug; + name: string; + icon: IconName; + eyebrow: string; + headline: string; + description: string; + challenge: string; + outcome: string; + cover: string; + idealFor: string; + deliverables: readonly string[]; + benefits: readonly { title: string; description: string }[]; + workflow: readonly { title: string; description: string; toolId: string }[]; + modelCategories: readonly PricingCategory[]; + questions: readonly { question: string; answer: string }[]; + related: readonly SolutionSlug[]; +}; + +export const solutions: readonly Solution[] = [ + { + slug: "marketing-agencies", + name: "Marketing agencies", + icon: "director", + eyebrow: "From first brief to client-ready assets", + headline: "A complete campaign begins with a clearer creative direction.", + description: "Shape client briefs into a connected production journey: concept, script, campaign visuals, motion, narration and reusable deliverables.", + challenge: "Too many handoffs, revisions and disconnected tools can slow down creative delivery. Bring the planning and media work closer together.", + outcome: "Move from a rough client request to a coordinated set of campaign concepts and production assets.", + cover: "/solution-art/marketing-agencies.svg", + idealFor: "Creative agencies, freelancers, account teams and brand campaign producers", + deliverables: ["Creative brief", "Campaign key visuals", "Video concepts", "Voiceover drafts", "Organised campaign assets"], + benefits: [ + { title: "Present more than one direction", description: "Explore campaign concepts and visual options before committing to production." }, + { title: "Carry an idea across media", description: "Move from scripts and still images into model-supported video and narration." }, + { title: "Keep revisions connected", description: "Save generated work with project context rather than treating every output as a one-off." }, + ], + workflow: [ + { title: "Interpret the brief", description: "Define the audience, campaign objective and visual direction.", toolId: "creative-director" }, + { title: "Develop the creative", description: "Turn the direction into a campaign concept and key image.", toolId: "image-studio" }, + { title: "Build motion", description: "Explore supported video concepts and scene references.", toolId: "video-studio" }, + { title: "Give it a voice", description: "Create narration and carry the results into the asset library.", toolId: "voice-studio" }, + ], + modelCategories: ["chat", "image", "video", "speech", "tools"], + questions: [ + { question: "Can I work on multiple client campaigns?", answer: "Creators provides projects, assets and organization-scoped workspaces to keep creative work organized. Apply your own client access and rights process." }, + { question: "Can I preview costs before generation?", answer: "The authenticated Studio displays a quote for supported settings before submitting billable work. The public pricing page lists currently published reference rates." }, + { question: "Is the sample campaign a customer case study?", answer: "No. The campaign visuals and examples on this page are illustrative creative demonstrations, not customer testimonials or guaranteed results." }, + ], + related: ["production-teams", "businesses"], + }, + { + slug: "ecommerce", + name: "E-commerce & retail", + icon: "image", + eyebrow: "Make more of every product", + headline: "Turn your products into compelling creative campaigns.", + description: "Develop product visual directions, lifestyle compositions, short promotional concepts and delivery-ready media from permitted source material.", + challenge: "A product needs different creative assets for listings, launches and promotions. Starting a new production process for every format is inefficient.", + outcome: "Create a coordinated visual story around a product, then adapt it for the next campaign.", + cover: "/solution-art/ecommerce.svg", + idealFor: "Online retailers, product marketers, catalog teams and emerging brands", + deliverables: ["Product concepts", "Lifestyle scenes", "Campaign variations", "Product motion concepts", "Prepared media assets"], + benefits: [ + { title: "Explore more settings", description: "Try creative environments and treatments without rebuilding each brief from scratch." }, + { title: "Adapt across formats", description: "Plan campaign assets for store pages, social posts and promotional stories." }, + { title: "Refine existing assets", description: "Use supported editing and media operations to prepare source material for delivery." }, + ], + workflow: [ + { title: "Begin with your product", description: "Start from a source image you have permission to use.", toolId: "image-studio" }, + { title: "Create campaign variations", description: "Generate a coordinated set of product visual concepts.", toolId: "precision-image" }, + { title: "Explore motion", description: "Develop a model-supported product reveal or short visual sequence.", toolId: "video-studio" }, + { title: "Prepare the result", description: "Use eligible finishing operations for publication.", toolId: "mediakit" }, + ], + modelCategories: ["image", "video", "tools"], + questions: [ + { question: "Will AI always preserve my product exactly?", answer: "No. Generated content must be reviewed against real product details, packaging and applicable advertising requirements before publishing." }, + { question: "Can I start with existing product photos?", answer: "Supported image and editing workflows accept permitted source material. Available controls depend on the selected enabled model." }, + { question: "Where can I compare costs?", answer: "Browse the public pricing page for published rates, then check the authenticated quote for the selected settings before generation." }, + ], + related: ["businesses", "marketing-agencies"], + }, + { + slug: "social-media-creators", + name: "Social media creators", + icon: "video", + eyebrow: "Make the next idea happen", + headline: "One content idea. More ways to tell it.", + description: "Draft hooks and scripts, create visuals, explore short-form motion, record AI narration and prepare assets for different social formats.", + challenge: "A consistent content schedule means starting, adapting and finishing ideas repeatedly across formats.", + outcome: "Build a repeatable creative flow from a short idea into a set of content-ready assets.", + cover: "/solution-art/social-media-creators.svg", + idealFor: "Short-form creators, video storytellers and personal brands", + deliverables: ["Content ideas", "Script drafts", "Thumbnail concepts", "Vertical video concepts", "Voiceovers", "Captions and transcripts"], + benefits: [ + { title: "Start from a small prompt", description: "Develop a rough topic into creative direction and draft scripts." }, + { title: "Try multiple visual directions", description: "Create image and motion concepts for the right audience and format." }, + { title: "Reuse what you make", description: "Keep source media and generated outputs available for revisions and later content." }, + ], + workflow: [ + { title: "Find the angle", description: "Turn a topic into a compelling short-form script.", toolId: "scriptwriter" }, + { title: "Create the visual", description: "Explore original image and thumbnail concepts.", toolId: "image-studio" }, + { title: "Bring it into motion", description: "Generate short visual sequences with supported model settings.", toolId: "video-studio" }, + { title: "Finish the message", description: "Create narration and use eligible transcription for captions.", toolId: "transcription" }, + ], + modelCategories: ["chat", "image", "video", "speech", "audio", "transcription"], + questions: [ + { question: "Does this publish directly to social platforms?", answer: "This showcase describes content creation and preparation, not automated publishing to third-party social networks." }, + { question: "Can I make vertical video?", answer: "Enabled models expose their supported aspect ratios and duration controls in the Studio. Choose the configuration appropriate for your destination." }, + { question: "Can I generate captions?", answer: "Creators provides transcription workflows and supported subtitle exports for eligible source recordings." }, + ], + related: ["marketing-agencies", "production-teams"], + }, + { + slug: "businesses", + name: "Businesses & brands", + icon: "brand", + eyebrow: "Your business has a story to tell", + headline: "Professional creative content without a complicated process.", + description: "Plan announcements, campaign visuals, explainers and brand narratives in a workspace that connects creative direction with production.", + challenge: "Marketing needs do not stop when a business has no dedicated video team, designer or writer available for every request.", + outcome: "Make it easier to explore and produce professional-looking creative assets for ongoing business communication.", + cover: "/solution-art/businesses.svg", + idealFor: "Growing businesses, service companies, local brands and in-house marketers", + deliverables: ["Brand concepts", "Announcement visuals", "Explainer scripts", "Spokesperson concepts", "Reusable marketing assets"], + benefits: [ + { title: "Begin with the message", description: "Shape product and service announcements into clearer creative briefs." }, + { title: "Explore visual options", description: "Produce image, motion and narration concepts in one workspace." }, + { title: "Understand spending", description: "Compare published customer prices and review quotes before supported jobs." }, + ], + workflow: [ + { title: "Set your brand direction", description: "Describe the message, audience and tone.", toolId: "brand-story" }, + { title: "Create promotional artwork", description: "Explore original imagery for the campaign.", toolId: "image-studio" }, + { title: "Explain your offer", description: "Draft an explainer script and suitable narration.", toolId: "scriptwriter" }, + { title: "Present it visually", description: "Explore consent-based presenter workflows where supported.", toolId: "spokesperson" }, + ], + modelCategories: ["chat", "image", "video", "speech", "avatar"], + questions: [ + { question: "Do I need to know AI model names?", answer: "No. Start with the outcome and use the public tools directory to find an appropriate creative workflow." }, + { question: "Can a business collaborate with its team?", answer: "Creators supports organization-scoped workspaces with members and creative asset organization." }, + { question: "Does a spokesperson require consent?", answer: "Yes. Only provide portrait and driving material you have permission and appropriate consent to use." }, + ], + related: ["ecommerce", "marketing-agencies"], + }, + { + slug: "production-teams", + name: "Content production teams", + icon: "projects", + eyebrow: "Make the whole process connect", + headline: "Connect your creative production from planning to delivery.", + description: "Bring together scripts, storyboards, generation, voice, editing, reusable assets and supported storage so ideas do not disappear between stages.", + challenge: "Disconnected applications and isolated output files make it harder to preserve context and iterate across an entire production.", + outcome: "Build a more connected workflow for turning production plans into finished media assets.", + cover: "/solution-art/production-teams.svg", + idealFor: "Studios, creative operations, campaign producers and in-house content teams", + deliverables: ["Creative plans", "Storyboards", "Source media", "Generated clips", "Narration", "Edited sequences", "Project assets"], + benefits: [ + { title: "Plan with continuity", description: "Connect creative briefs, scripts and story development to subsequent production work." }, + { title: "Choose tools for each task", description: "Use the enabled model and workflow appropriate to the media requirement." }, + { title: "Keep outputs discoverable", description: "Organize assets with their generation provenance, projects and supported storage." }, + ], + workflow: [ + { title: "Plan production", description: "Shape a structured brief and shot direction.", toolId: "creative-director" }, + { title: "Generate the media", description: "Create supported visuals and motion from your production inputs.", toolId: "video-studio" }, + { title: "Edit the sequence", description: "Arrange, refine and preview your clips.", toolId: "video-editor" }, + { title: "Prepare deliverables", description: "Use supported finishing operations and keep outputs organized.", toolId: "mediakit" }, + ], + modelCategories: ["chat", "image", "video", "speech", "audio", "transcription", "avatar", "tools"], + questions: [ + { question: "Is this a replacement for every professional editing system?", answer: "No. Creators connects its own supported planning, media generation, editing and finishing workflows. Choose specialist software where a production requires additional controls." }, + { question: "Does the platform track generated assets?", answer: "Generated media can be saved as workspace assets with provenance and organized through projects, folders and history." }, + { question: "Can we connect external storage?", answer: "Supported platform and bring-your-own storage connections are managed inside the authenticated workspace, subject to configuration and access." }, + ], + related: ["marketing-agencies", "social-media-creators"], + }, +]; + +export function solutionBySlug(slug: string): Solution | undefined { + return solutions.find((item) => item.slug === slug); +} + +export function solutionHref(solution: Pick): string { + return "/solutions/" + solution.slug; +} + +export type FinderAudience = SolutionSlug; +export type FinderGoal = "campaign" | "product" | "social" | "explainer" | "finish"; +export type FinderPriority = "speed" | "quality" | "manage" | "cost"; +const goalTool: Record = { + campaign: "creative-director", + product: "image-studio", + social: "video-studio", + explainer: "scriptwriter", + finish: "mediakit", +}; +export function recommendSolution(audience: FinderAudience, goal: FinderGoal, priority: FinderPriority) { + const solution = solutionBySlug(audience); + if (!solution) return null; + const preferredId = goalTool[goal]; + const preferred = publicToolById(preferredId); + const workflow = solution.workflow.map((step) => publicToolById(step.toolId)).filter((tool) => tool !== undefined); + const tools = [preferred, ...workflow].filter((tool) => tool !== undefined).filter((tool, index, all) => all.findIndex((candidate) => candidate.id === tool.id) === index).slice(0, 3); + return { + solution, + tools, + advice: ({ + speed: "Start with a focused tool or template, then expand your concept as needed.", + quality: "Explore the direction first, use permitted references and review each generated result.", + manage: "Keep working assets and production context together in your project.", + cost: "Check published customer prices and review the authenticated quote before billable work.", + } as const)[priority], + }; +} + +export function relevantModels(items: readonly PublicPricingItem[], categories: readonly PricingCategory[]): PublicPricingItem[] { + return items.filter((item) => categories.includes(item.category)); +} From 079c23ad0f274e751e6af0b76a88727671d38eb7 Mon Sep 17 00:00:00 2001 From: "Sameer .I. Siddiqui" Date: Mon, 12 Oct 2026 13:38:45 +0530 Subject: [PATCH 2/6] feat(solutions): guided finder, editorial components and live category model tables --- .../solutions/solution-components.tsx | 53 ++++++++++ .../marketing/solutions/solution-models.tsx | 99 +++++++++++++++++++ .../marketing/solutions/solutions-finder.tsx | 93 +++++++++++++++++ 3 files changed, 245 insertions(+) create mode 100644 apps/web/src/components/marketing/solutions/solution-components.tsx create mode 100644 apps/web/src/components/marketing/solutions/solution-models.tsx create mode 100644 apps/web/src/components/marketing/solutions/solutions-finder.tsx diff --git a/apps/web/src/components/marketing/solutions/solution-components.tsx b/apps/web/src/components/marketing/solutions/solution-components.tsx new file mode 100644 index 00000000..569a0071 --- /dev/null +++ b/apps/web/src/components/marketing/solutions/solution-components.tsx @@ -0,0 +1,53 @@ +import type { Route } from "next"; +import Image from "next/image"; +import Link from "next/link"; +import { Icon } from "@/components/ui/icon"; +import { ToolsConversion, ToolsFooter, ToolsHeader } from "@/components/marketing/tools/tools-shell"; +import { solutionHref, type Solution } from "@/lib/public-solutions"; + +export { ToolsHeader as SolutionsHeader, ToolsFooter as SolutionsFooter, ToolsConversion as SolutionsConversion }; + +export function SectionTitle({ eyebrow, title, description }: { eyebrow: string; title: string; description?: string }) { + return ( +
+

{eyebrow}

+

{title}

+ {description ?

{description}

: null} +
+ ); +} + +export function SolutionCard({ solution }: { solution: Solution }) { + return ( +
+ + {solution.name + +
+
+ +

{solution.name}

+
+

{solution.description}

+ + See the solution + +
+
+ ); +} + +export function SolutionFaq({ questions }: { questions: readonly { question: string; answer: string }[] }) { + return ( +
+ {questions.map((entry) => ( +
+ + {entry.question} + +

{entry.answer}

+
+ ))} +
+ ); +} diff --git a/apps/web/src/components/marketing/solutions/solution-models.tsx b/apps/web/src/components/marketing/solutions/solution-models.tsx new file mode 100644 index 00000000..f55550ba --- /dev/null +++ b/apps/web/src/components/marketing/solutions/solution-models.tsx @@ -0,0 +1,99 @@ +import Link from "next/link"; +import type { PricingCategory, PublicPricingItem } from "@/lib/public-pricing-contract"; +import { pricingCategories } from "@/lib/public-pricing-contract"; +import { relevantModels } from "@/lib/public-solutions"; +import { Icon, type IconName } from "@/components/ui/icon"; +import { SectionTitle } from "./solution-components"; + +const icons: Record = { + chat: "chat", image: "image", video: "video", speech: "voice", + audio: "voice", transcription: "script", avatar: "user", tools: "wand", +}; + +function PriceCell({ model }: { model: PublicPricingItem }) { + if (model.tiers.length > 0) { + return ( +
+ {model.tiers.map((tier) => ( +

+ Up to {tier.maxPromptTokens.toLocaleString("en-US")} context: + {tier.inputCreditsPerMillion} in / {tier.outputCreditsPerMillion} out credits per 1M tokens +

+ ))} +
+ ); + } + return ( +
+

{model.baseCredits} credits

+

{model.baseOmr}

+ {model.exampleOnly ?

Reference only; settings affect final quote

: null} +
+ ); +} + +export function SolutionModels({ models, categories, compact = false }: { + models: readonly PublicPricingItem[]; + categories: readonly PricingCategory[]; + compact?: boolean; +}) { + const selected = relevantModels(models, categories); + return ( +
+ + +
+ {pricingCategories.filter((category) => categories.includes(category.id)).map((category) => { + const rows = selected.filter((model) => model.category === category.id); + return ( +
+
+ +

{category.label}

{category.description}

+
+ {rows.length ? ( +
+ + + + + + + + + + + + + {(compact ? rows.slice(0, 5) : rows).map((model) => ( + + + + + + + + ))} + +
{category.label} currently published model and operation rates
Model / operationProviderCapabilities & available inBilling basisPublished customer reference
{model.name}{model.provider} + {model.capabilities.slice(0, 4).join(" · ") || model.description} + {model.availableIn.join(", ")} + {model.dimension} / {model.unitQuantity}
+ {compact && rows.length > 5 ?

Showing 5 of {rows.length} entries. Browse Pricing for every published model.

: null} +
+ ) : ( +
No configured models with published customer pricing are currently listed in this category. Check current pricing →
+ )} +
+ ); + })} +
+

Variable duration, resolution, inputs, reference materials and token usage may change the price. Only the authenticated Studio quote is authoritative before a generation is submitted.

+ Explore all customer pricing +
+ ); +} diff --git a/apps/web/src/components/marketing/solutions/solutions-finder.tsx b/apps/web/src/components/marketing/solutions/solutions-finder.tsx new file mode 100644 index 00000000..4a469657 --- /dev/null +++ b/apps/web/src/components/marketing/solutions/solutions-finder.tsx @@ -0,0 +1,93 @@ +"use client"; + +import { useState } from "react"; +import Link from "next/link"; +import type { Route } from "next"; +import { Icon } from "@/components/ui/icon"; +import { recommendSolution, solutionHref, solutions, type FinderAudience, type FinderGoal, type FinderPriority } from "@/lib/public-solutions"; + +const goals: readonly { id: FinderGoal; title: string }[] = [ + { id: "campaign", title: "Create a complete campaign" }, + { id: "product", title: "Create product visuals" }, + { id: "social", title: "Make social content" }, + { id: "explainer", title: "Produce an explainer" }, + { id: "finish", title: "Improve existing media" }, +]; +const priorities: readonly { id: FinderPriority; title: string }[] = [ + { id: "speed", title: "Speed and simplicity" }, + { id: "quality", title: "Creative quality and control" }, + { id: "manage", title: "Managing several deliverables" }, + { id: "cost", title: "Understanding the costs" }, +]; + +function ChoiceGroup({ title, options, value, onChange }: { + title: string; + options: readonly { id: T; title: string }[]; + value: T | ""; + onChange: (value: T) => void; +}) { + return ( +
+ {title} +
+ {options.map((option) => ( + + ))} +
+
+ ); +} + +export function SolutionsFinder() { + const [audience, setAudience] = useState(""); + const [goal, setGoal] = useState(""); + const [priority, setPriority] = useState(""); + const [showResult, setShowResult] = useState(false); + const result = showResult && audience && goal && priority ? recommendSolution(audience, goal, priority) : null; + return ( +
+
+ +
+ ); +} From 3f096f727b7dcea4aaec00e1590f4306bce5d48a Mon Sep 17 00:00:00 2001 From: "Sameer .I. Siddiqui" Date: Mon, 12 Oct 2026 13:40:03 +0530 Subject: [PATCH 3/6] feat(solutions): six public pages, indexed model tables and browser acceptance tests --- apps/web/src/app/solutions/[slug]/page.tsx | 149 +++++++++++++++++++++ apps/web/src/app/solutions/page.tsx | 135 +++++++++++++++++++ docs/solutions-2.md | 13 ++ e2e/solutions.spec.ts | 41 ++++++ 4 files changed, 338 insertions(+) create mode 100644 apps/web/src/app/solutions/[slug]/page.tsx create mode 100644 apps/web/src/app/solutions/page.tsx create mode 100644 docs/solutions-2.md create mode 100644 e2e/solutions.spec.ts diff --git a/apps/web/src/app/solutions/[slug]/page.tsx b/apps/web/src/app/solutions/[slug]/page.tsx new file mode 100644 index 00000000..ad7b6372 --- /dev/null +++ b/apps/web/src/app/solutions/[slug]/page.tsx @@ -0,0 +1,149 @@ +import type { Metadata, Route } from "next"; +import Image from "next/image"; +import Link from "next/link"; +import { notFound } from "next/navigation"; +import { Icon } from "@/components/ui/icon"; +import { SectionTitle, SolutionCard, SolutionFaq, SolutionsConversion, SolutionsFooter, SolutionsHeader } from "@/components/marketing/solutions/solution-components"; +import { SolutionModels } from "@/components/marketing/solutions/solution-models"; +import { getPublicPricingCatalogue } from "@/lib/public-pricing"; +import { publicToolById } from "@/lib/public-tools"; +import { solutionBySlug, solutions, solutionHref } from "@/lib/public-solutions"; + +export const dynamic = "force-dynamic"; +type PageProps = { params: Promise<{ slug: string }> }; +export function generateStaticParams() { + return solutions.map((solution) => ({ slug: solution.slug })); +} +export async function generateMetadata({ params }: PageProps): Promise { + const solution = solutionBySlug((await params).slug); + if (!solution) return { title: "Solution not found", robots: { index: false } }; + return { + title: solution.name + " — Creative AI Workflows & Outcomes", + description: solution.description.slice(0, 160), + alternates: { canonical: solutionHref(solution) }, + robots: { index: true, follow: true }, + openGraph: { + title: solution.headline + " · Aiwa Creators", + description: solution.description, type: "website", + images: [{ url: solution.cover, width: 920, height: 650, alt: solution.name + " illustration" }], + }, + }; +} + +export default async function SolutionDetailPage({ params }: PageProps) { + const solution = solutionBySlug((await params).slug); + if (!solution) notFound(); + const catalogue = await getPublicPricingCatalogue().catch(() => ({ models: [] })); + const origin = process.env.APP_URL ?? "https://creator.aiwamediagroup.com"; + const breadcrumbs = [ + { name: "Home", item: "/" }, + { name: "Solutions", item: "/solutions" }, + { name: solution.name, item: solutionHref(solution) }, + ]; + const schema = { + "@context": "https://schema.org", + "@type": "BreadcrumbList", + itemListElement: breadcrumbs.map((item, i) => ({ + "@type": "ListItem", position: i + 1, name: item.name, + item: new URL(item.item, origin).toString(), + })), + }; + const related = solution.related.map(solutionBySlug).filter((item) => item !== undefined); + return ( +
+ +