kopia lustrzana https://github.com/transitive-bullshit/chatgpt-api
feat: webapp work
rodzic
db4d24a452
commit
8d328b69fc
Plik binarny nie jest wyświetlany.
Po Szerokość: | Wysokość: | Rozmiar: 400 KiB |
|
@ -1,5 +1,6 @@
|
|||
import Link from 'next/link'
|
||||
|
||||
import { PageContainer } from '@/components/page-container'
|
||||
import { SupplySideCTA } from '@/components/supply-side-cta'
|
||||
import { githubUrl, twitterUrl } from '@/lib/config'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
@ -8,7 +9,7 @@ import styles from './styles.module.css'
|
|||
|
||||
export default function AboutPage() {
|
||||
return (
|
||||
<>
|
||||
<PageContainer>
|
||||
<h1 className='text-center text-balance leading-snug md:leading-none text-4xl font-semibold'>
|
||||
About
|
||||
</h1>
|
||||
|
@ -185,6 +186,6 @@ export default function AboutPage() {
|
|||
|
||||
<SupplySideCTA variant='github-2' />
|
||||
</section>
|
||||
</>
|
||||
</PageContainer>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
import { AppConsumersList } from '@/components/app-consumers-list'
|
||||
import { AppProjectsList } from '@/components/app-projects-list'
|
||||
import { PageContainer } from '@/components/page-container'
|
||||
|
||||
export function AppDashboard() {
|
||||
return (
|
||||
<>
|
||||
<PageContainer>
|
||||
<section>
|
||||
<h1
|
||||
className='text-center text-balance leading-snug md:leading-none
|
||||
|
@ -12,12 +13,12 @@ export function AppDashboard() {
|
|||
Dashboard
|
||||
</h1>
|
||||
|
||||
<div className='flex gap-8 space-around'>
|
||||
<div className='flex flex-col lg:flex-row gap-8 space-around'>
|
||||
<AppConsumersList />
|
||||
|
||||
<AppProjectsList />
|
||||
</div>
|
||||
</section>
|
||||
</>
|
||||
</PageContainer>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -47,10 +47,10 @@ export default function RootLayout({
|
|||
<html lang='en' suppressHydrationWarning>
|
||||
<body className={`${geist.variable} antialiased`}>
|
||||
<Providers>
|
||||
<div className='w-full min-h-[100vh] relative flex flex-col items-center'>
|
||||
<div className='relative w-full min-h-[100vh] flex flex-col items-center'>
|
||||
<Header />
|
||||
|
||||
<main className='flex-1 w-full flex flex-col items-center max-w-[1200px] gap-16 pt-16 pb-24 px-2 overflow-x-hidden'>
|
||||
<main className='relative w-full flex-1 flex flex-col items-center gap-16 pt-16 pb-24 px-2 overflow-x-hidden'>
|
||||
{children}
|
||||
</main>
|
||||
|
||||
|
|
|
@ -1,11 +1,15 @@
|
|||
import { Suspense } from 'react'
|
||||
|
||||
import { PageContainer } from '@/components/page-container'
|
||||
|
||||
import { LoginForm } from './login-form'
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
<Suspense>
|
||||
<LoginForm />
|
||||
<PageContainer>
|
||||
<LoginForm />
|
||||
</PageContainer>
|
||||
</Suspense>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ import useInfiniteScroll from 'react-infinite-scroll-hook'
|
|||
|
||||
import { useAgentic } from '@/components/agentic-provider'
|
||||
import { LoadingIndicator } from '@/components/loading-indicator'
|
||||
import { PageContainer } from '@/components/page-container'
|
||||
import { PublicProject } from '@/components/public-project'
|
||||
import { useInfiniteQuery } from '@/lib/query-client'
|
||||
|
||||
|
@ -51,7 +52,7 @@ export function MarketplaceIndex() {
|
|||
const projects = data ? data.pages.flatMap((p) => p.projects) : []
|
||||
|
||||
return (
|
||||
<>
|
||||
<PageContainer>
|
||||
<section>
|
||||
<h1
|
||||
className='text-center text-balance leading-snug md:leading-none
|
||||
|
@ -74,7 +75,7 @@ export function MarketplaceIndex() {
|
|||
Please refresh or contact support.
|
||||
</p>
|
||||
) : (
|
||||
<div className='grid gap-4'>
|
||||
<div className='grid grid-cols grid-cols-1 gap-4 sm:grid-cols-2 xl:grid-cols-3'>
|
||||
{projects.map((project) => (
|
||||
<PublicProject key={project.id} project={project} />
|
||||
))}
|
||||
|
@ -89,6 +90,6 @@ export function MarketplaceIndex() {
|
|||
</div>
|
||||
)}
|
||||
</section>
|
||||
</>
|
||||
</PageContainer>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
'use client'
|
||||
|
||||
import { assert, omit, sanitizeSearchParams } from '@agentic/platform-core'
|
||||
import { Loader2Icon } from 'lucide-react'
|
||||
import { useRouter, useSearchParams } from 'next/navigation'
|
||||
import { useCallback, useEffect, useRef, useState } from 'react'
|
||||
|
||||
import { useAgentic } from '@/components/agentic-provider'
|
||||
import { LoadingIndicator } from '@/components/loading-indicator'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { PageContainer } from '@/components/page-container'
|
||||
import { ProjectPricingPlans } from '@/components/project-pricing-plans'
|
||||
import { toast, toastError } from '@/lib/notifications'
|
||||
import { useQuery } from '@/lib/query-client'
|
||||
|
||||
|
@ -139,72 +139,42 @@ export function MarketplaceProjectIndex({
|
|||
])
|
||||
|
||||
return (
|
||||
<section>
|
||||
{!ctx || isLoading ? (
|
||||
<LoadingIndicator />
|
||||
) : isError ? (
|
||||
<p>Error fetching project</p>
|
||||
) : !project ? (
|
||||
<p>Project "{projectIdentifier}" not found</p>
|
||||
) : (
|
||||
<>
|
||||
<h1
|
||||
className='text-center text-balance leading-snug md:leading-none
|
||||
<PageContainer>
|
||||
<section>
|
||||
{!ctx || isLoading ? (
|
||||
<LoadingIndicator />
|
||||
) : isError ? (
|
||||
<p>Error fetching project</p>
|
||||
) : !project ? (
|
||||
<p>Project "{projectIdentifier}" not found</p>
|
||||
) : (
|
||||
<>
|
||||
<h1
|
||||
className='text-center text-balance leading-snug md:leading-none
|
||||
text-4xl font-extrabold'
|
||||
>
|
||||
Project {project.name}
|
||||
</h1>
|
||||
>
|
||||
Project {project.name}
|
||||
</h1>
|
||||
|
||||
<div className='mt-8'>
|
||||
<pre className='max-w-lg'>
|
||||
{JSON.stringify(
|
||||
omit(project, 'lastPublishedDeployment', 'lastDeployment'),
|
||||
null,
|
||||
2
|
||||
)}
|
||||
</pre>
|
||||
</div>
|
||||
|
||||
<div className='mt-8'>
|
||||
<h2 className='text-center text-balance leading-snug md:leading-none text-2xl font-extrabold mb-4'>
|
||||
Pricing Plans
|
||||
</h2>
|
||||
|
||||
<div className='flex gap-8'>
|
||||
{project.lastPublishedDeployment!.pricingPlans.map((plan) => (
|
||||
<div key={plan.slug} className='flex flex-col gap-4'>
|
||||
<h3 className='text-center text-balance leading-snug md:leading-none text-xl font-bold'>
|
||||
{plan.name}
|
||||
</h3>
|
||||
|
||||
<pre className='max-w-lg'>
|
||||
{JSON.stringify(plan, null, 2)}
|
||||
</pre>
|
||||
|
||||
<Button
|
||||
onClick={() => onSubscribe(plan.slug)}
|
||||
// TODO: handle free plans correctly
|
||||
disabled={
|
||||
consumer?.plan === plan.slug ||
|
||||
!!isLoadingStripeCheckoutForPlan
|
||||
}
|
||||
>
|
||||
{isLoadingStripeCheckoutForPlan === plan.slug && (
|
||||
<Loader2Icon className='animate-spin mr-2' />
|
||||
)}
|
||||
|
||||
{consumer?.plan === plan.slug ? (
|
||||
<span>Currently subscribed to "{plan.name}"</span>
|
||||
) : (
|
||||
<span>Subscribe to "{plan.name}"</span>
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
))}
|
||||
<div className='mt-8'>
|
||||
<pre className='max-w-lg'>
|
||||
{JSON.stringify(
|
||||
omit(project, 'lastPublishedDeployment', 'lastDeployment'),
|
||||
null,
|
||||
2
|
||||
)}
|
||||
</pre>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</section>
|
||||
|
||||
<ProjectPricingPlans
|
||||
project={project}
|
||||
consumer={consumer}
|
||||
isLoadingStripeCheckoutForPlan={isLoadingStripeCheckoutForPlan}
|
||||
onSubscribe={onSubscribe}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</section>
|
||||
</PageContainer>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ import { ExampleUsage } from '@/components/example-usage'
|
|||
import { GitHubStarCounter } from '@/components/github-star-counter'
|
||||
import { HeroSimulation2 } from '@/components/hero-simulation-2'
|
||||
import { MCPMarketplaceFeatures } from '@/components/mcp-marketplace-features'
|
||||
import { PageContainer } from '@/components/page-container'
|
||||
import { githubUrl, twitterUrl } from '@/lib/config'
|
||||
import {
|
||||
defaultConfig,
|
||||
|
@ -35,22 +36,20 @@ export default async function TheBestDamnLandingPageEver() {
|
|||
const initialCodeBlock = await highlight(initialCodeSnippet)
|
||||
|
||||
return (
|
||||
<>
|
||||
<PageContainer>
|
||||
{/* Hero section */}
|
||||
<DotsSection className='mb-16'>
|
||||
<div className='flex flex-col gap-10 relative z-10'>
|
||||
<h1 className='text-center text-balance leading-snug md:leading-none text-4xl font-semibold'>
|
||||
The App Store for LLM Tools
|
||||
</h1>
|
||||
<section className='flex flex-col gap-10 mb-16'>
|
||||
<h1 className='text-center text-balance leading-snug md:leading-none text-4xl font-semibold'>
|
||||
The App Store for LLM Tools
|
||||
</h1>
|
||||
|
||||
<h5 className='text-center text-lg max-w-2xl'>
|
||||
Agentic is a curated marketplace of production-grade LLM tools. All
|
||||
tools are exposed as both MCP servers as well as simple HTTP APIs.
|
||||
</h5>
|
||||
<h5 className='text-center text-lg max-w-2xl'>
|
||||
Agentic is a curated marketplace of production-grade LLM tools. All
|
||||
tools are exposed as both MCP servers as well as simple HTTP APIs.
|
||||
</h5>
|
||||
|
||||
<DemandSideCTA />
|
||||
</div>
|
||||
</DotsSection>
|
||||
<DemandSideCTA />
|
||||
</section>
|
||||
|
||||
{/* How it works section */}
|
||||
<section className='flex flex-col gap-8 mb-16'>
|
||||
|
@ -163,6 +162,6 @@ export default async function TheBestDamnLandingPageEver() {
|
|||
<DemandSideCTA />
|
||||
</div>
|
||||
</DotsSection>
|
||||
</>
|
||||
</PageContainer>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -4,28 +4,27 @@ import { DotsSection } from '@/components/dots-section'
|
|||
import { ExampleAgenticConfigs } from '@/components/example-agentic-configs'
|
||||
import { GitHubStarCounter } from '@/components/github-star-counter'
|
||||
import { MCPGatewayFeatures } from '@/components/mcp-gateway-features'
|
||||
import { PageContainer } from '@/components/page-container'
|
||||
import { SupplySideCTA } from '@/components/supply-side-cta'
|
||||
import { githubUrl, twitterUrl } from '@/lib/config'
|
||||
|
||||
export default function MCPAuthorsPage() {
|
||||
export default function PublishingMCPsPage() {
|
||||
return (
|
||||
<>
|
||||
<PageContainer>
|
||||
{/* Hero section */}
|
||||
<DotsSection className='mb-16'>
|
||||
<div className='flex flex-col gap-8 relative z-10'>
|
||||
<h1 className='text-center text-balance leading-snug md:leading-none text-4xl font-semibold'>
|
||||
Your API → Paid MCP, Instantly
|
||||
</h1>
|
||||
<section className='flex flex-col gap-8 mb-16'>
|
||||
<h1 className='text-center text-balance leading-snug md:leading-none text-4xl font-semibold'>
|
||||
Your API → Paid MCP, Instantly
|
||||
</h1>
|
||||
|
||||
<h5 className='text-center text-lg max-w-2xl'>
|
||||
Run one command to turn any MCP server or OpenAPI service into a
|
||||
paid MCP product,{' '}
|
||||
<em>with built-in distribution to over 20k AI engineers</em>.
|
||||
</h5>
|
||||
<h5 className='text-center text-lg max-w-2xl'>
|
||||
Run one command to turn any MCP server or OpenAPI service into a paid
|
||||
MCP product,{' '}
|
||||
<em>with built-in distribution to over 20k AI engineers</em>.
|
||||
</h5>
|
||||
|
||||
<SupplySideCTA />
|
||||
</div>
|
||||
</DotsSection>
|
||||
<SupplySideCTA />
|
||||
</section>
|
||||
|
||||
{/* How it works section */}
|
||||
<section className='flex flex-col gap-8 mb-16'>
|
||||
|
@ -109,6 +108,6 @@ export default function MCPAuthorsPage() {
|
|||
<SupplySideCTA variant='github-2' />
|
||||
</div>
|
||||
</DotsSection>
|
||||
</>
|
||||
</PageContainer>
|
||||
)
|
||||
}
|
|
@ -1,11 +1,15 @@
|
|||
import { Suspense } from 'react'
|
||||
|
||||
import { PageContainer } from '@/components/page-container'
|
||||
|
||||
import { SignupForm } from './signup-form'
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
<Suspense>
|
||||
<SignupForm />
|
||||
<PageContainer>
|
||||
<SignupForm />
|
||||
</PageContainer>
|
||||
</Suspense>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -68,7 +68,7 @@ export function AppConsumersList() {
|
|||
started.
|
||||
</p>
|
||||
) : (
|
||||
<div className='grid gap-4'>
|
||||
<div className='grid grid-cols grid-cols-1 gap-4 sm:grid-cols-2 xl:grid-cols-3'>
|
||||
{consumers.map((consumer) => (
|
||||
<Link
|
||||
key={consumer.id}
|
||||
|
|
|
@ -63,7 +63,7 @@ export function AppProjectsList() {
|
|||
) : !projects.length ? (
|
||||
<p>No projects found. Create your first project to get started.</p>
|
||||
) : (
|
||||
<div className='grid gap-4'>
|
||||
<div className='grid grid-cols grid-cols-1 gap-4 sm:grid-cols-2 xl:grid-cols-3'>
|
||||
{projects.map((project) => (
|
||||
<Link
|
||||
key={project.id}
|
||||
|
|
|
@ -28,8 +28,8 @@ export function Footer() {
|
|||
</span>
|
||||
|
||||
<span>
|
||||
<ActiveLink href='/mcp-authors' className='link'>
|
||||
For MCP Authors
|
||||
<ActiveLink href='/publishing' className='link'>
|
||||
Publishing MCPs
|
||||
</ActiveLink>
|
||||
</span>
|
||||
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
import { cn } from '@/lib/utils'
|
||||
|
||||
export function PageContainer({
|
||||
background = true,
|
||||
className,
|
||||
children
|
||||
}: {
|
||||
background?: boolean
|
||||
className?: string
|
||||
children: React.ReactNode
|
||||
}) {
|
||||
return (
|
||||
<>
|
||||
{background && (
|
||||
<div className='absolute top-0 left-0 w-[100vw] h-[100vh] bg-[url(/bg.png)] bg-top-right bg-no-repeat bg-size-[100vw_50vh] md:bg-auto z-0' />
|
||||
)}
|
||||
|
||||
<div
|
||||
className={cn(
|
||||
'relative w-full flex-1 flex flex-col items-center max-w-[1200px] gap-16 z-10',
|
||||
className
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
import type { Consumer, Project } from '@agentic/platform-types'
|
||||
import { Loader2Icon } from 'lucide-react'
|
||||
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
export function ProjectPricingPlans({
|
||||
project,
|
||||
consumer,
|
||||
isLoadingStripeCheckoutForPlan,
|
||||
onSubscribe,
|
||||
className
|
||||
}: {
|
||||
project: Project
|
||||
consumer?: Consumer
|
||||
isLoadingStripeCheckoutForPlan: string | null
|
||||
onSubscribe: (planSlug: string) => void
|
||||
className?: string
|
||||
}) {
|
||||
const numPricingPlans =
|
||||
project.lastPublishedDeployment?.pricingPlans.length || 1
|
||||
|
||||
return (
|
||||
<div className={cn('mt-8 flex flex-col gap-8', className)}>
|
||||
<h2 className='text-center text-balance leading-snug md:leading-none text-2xl font-extrabold'>
|
||||
Pricing Plans
|
||||
</h2>
|
||||
|
||||
<div
|
||||
className={`grid grid-cols grid-cols-1 gap-8 sm:grid-cols-${Math.min(
|
||||
2,
|
||||
numPricingPlans
|
||||
)} xl:grid-cols-${Math.min(3, numPricingPlans)}`}
|
||||
>
|
||||
{project.lastPublishedDeployment!.pricingPlans.map((plan) => (
|
||||
<div
|
||||
key={plan.slug}
|
||||
className='flex flex-col gap-4 p-4 border rounded-lg shadow-sm'
|
||||
>
|
||||
<h3 className='text-center text-balance leading-snug md:leading-none text-xl font-bold'>
|
||||
{plan.name}
|
||||
</h3>
|
||||
|
||||
<pre className='max-w-lg'>{JSON.stringify(plan, null, 2)}</pre>
|
||||
|
||||
<Button
|
||||
onClick={() => onSubscribe(plan.slug)}
|
||||
// TODO: handle free plans correctly
|
||||
disabled={
|
||||
consumer?.plan === plan.slug || !!isLoadingStripeCheckoutForPlan
|
||||
}
|
||||
>
|
||||
{isLoadingStripeCheckoutForPlan === plan.slug && (
|
||||
<Loader2Icon className='animate-spin mr-2' />
|
||||
)}
|
||||
|
||||
{consumer?.plan === plan.slug ? (
|
||||
<span>Currently subscribed to "{plan.name}"</span>
|
||||
) : (
|
||||
<span>Subscribe to "{plan.name}"</span>
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
Ładowanie…
Reference in New Issue