Skip to content

Commit 2c174ca

Browse files
authored
feat(landing): added models pages (#3888)
* feat(landing): added models pages * fix(models): address PR review feedback Correct model structured-data price bounds, remove dead code in the models catalog helpers, and harden OG font loading with graceful fallbacks. Made-with: Cursor * relative imports, build fix * lint * fix(models): remove dead og-utils exports, fix formatTokenCount null guard
1 parent ac831b8 commit 2c174ca

File tree

15 files changed

+2715
-51
lines changed

15 files changed

+2715
-51
lines changed

apps/sim/app/(home)/components/footer/footer.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ const RESOURCES_LINKS: FooterItem[] = [
2626
{ label: 'Blog', href: '/blog' },
2727
// { label: 'Templates', href: '/templates' },
2828
{ label: 'Docs', href: 'https://docs.sim.ai', external: true },
29+
{ label: 'Models', href: '/models' },
2930
// { label: 'Academy', href: '/academy' },
3031
{ label: 'Partners', href: '/partners' },
3132
{ label: 'Careers', href: 'https://jobs.ashbyhq.com/sim', external: true },
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
'use client'
2+
3+
import { useState } from 'react'
4+
import { ChevronDown } from '@/components/emcn'
5+
import { cn } from '@/lib/core/utils/cn'
6+
7+
export interface LandingFAQItem {
8+
question: string
9+
answer: string
10+
}
11+
12+
interface LandingFAQProps {
13+
faqs: LandingFAQItem[]
14+
}
15+
16+
export function LandingFAQ({ faqs }: LandingFAQProps) {
17+
const [openIndex, setOpenIndex] = useState<number | null>(0)
18+
19+
return (
20+
<div className='divide-y divide-[var(--landing-border)]'>
21+
{faqs.map(({ question, answer }, index) => {
22+
const isOpen = openIndex === index
23+
24+
return (
25+
<div key={question}>
26+
<button
27+
type='button'
28+
onClick={() => setOpenIndex(isOpen ? null : index)}
29+
className='flex w-full items-start justify-between gap-4 py-5 text-left'
30+
aria-expanded={isOpen}
31+
>
32+
<span
33+
className={cn(
34+
'font-[500] text-[15px] leading-snug transition-colors',
35+
isOpen
36+
? 'text-[var(--landing-text)]'
37+
: 'text-[var(--landing-text-muted)] hover:text-[var(--landing-text)]'
38+
)}
39+
>
40+
{question}
41+
</span>
42+
<ChevronDown
43+
className={cn(
44+
'mt-0.5 h-4 w-4 shrink-0 text-[#555] transition-transform duration-200',
45+
isOpen ? 'rotate-180' : 'rotate-0'
46+
)}
47+
aria-hidden='true'
48+
/>
49+
</button>
50+
51+
{isOpen && (
52+
<div className='pb-5'>
53+
<p className='text-[14px] text-[var(--landing-text-muted)] leading-[1.75]'>
54+
{answer}
55+
</p>
56+
</div>
57+
)}
58+
</div>
59+
)
60+
})}
61+
</div>
62+
)
63+
}
Lines changed: 2 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,58 +1,10 @@
1-
'use client'
2-
3-
import { useState } from 'react'
4-
import { ChevronDown } from '@/components/emcn'
5-
import { cn } from '@/lib/core/utils/cn'
1+
import { LandingFAQ } from '@/app/(landing)/components/landing-faq'
62
import type { FAQItem } from '@/app/(landing)/integrations/data/types'
73

84
interface IntegrationFAQProps {
95
faqs: FAQItem[]
106
}
117

128
export function IntegrationFAQ({ faqs }: IntegrationFAQProps) {
13-
const [openIndex, setOpenIndex] = useState<number | null>(0)
14-
15-
return (
16-
<div className='divide-y divide-[var(--landing-border)]'>
17-
{faqs.map(({ question, answer }, index) => {
18-
const isOpen = openIndex === index
19-
return (
20-
<div key={question}>
21-
<button
22-
type='button'
23-
onClick={() => setOpenIndex(isOpen ? null : index)}
24-
className='flex w-full items-start justify-between gap-4 py-5 text-left'
25-
aria-expanded={isOpen}
26-
>
27-
<span
28-
className={cn(
29-
'font-[500] text-[15px] leading-snug transition-colors',
30-
isOpen
31-
? 'text-[var(--landing-text)]'
32-
: 'text-[var(--landing-text-muted)] hover:text-[var(--landing-text)]'
33-
)}
34-
>
35-
{question}
36-
</span>
37-
<ChevronDown
38-
className={cn(
39-
'mt-0.5 h-4 w-4 shrink-0 text-[#555] transition-transform duration-200',
40-
isOpen ? 'rotate-180' : 'rotate-0'
41-
)}
42-
aria-hidden='true'
43-
/>
44-
</button>
45-
46-
{isOpen && (
47-
<div className='pb-5'>
48-
<p className='text-[14px] text-[var(--landing-text-muted)] leading-[1.75]'>
49-
{answer}
50-
</p>
51-
</div>
52-
)}
53-
</div>
54-
)
55-
})}
56-
</div>
57-
)
9+
return <LandingFAQ faqs={faqs} />
5810
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import { notFound } from 'next/navigation'
2+
import { createModelsOgImage } from '@/app/(landing)/models/og-utils'
3+
import {
4+
formatPrice,
5+
formatTokenCount,
6+
getModelBySlug,
7+
getProviderBySlug,
8+
} from '@/app/(landing)/models/utils'
9+
10+
export const runtime = 'edge'
11+
export const contentType = 'image/png'
12+
export const size = {
13+
width: 1200,
14+
height: 630,
15+
}
16+
17+
export default async function Image({
18+
params,
19+
}: {
20+
params: Promise<{ provider: string; model: string }>
21+
}) {
22+
const { provider: providerSlug, model: modelSlug } = await params
23+
const provider = getProviderBySlug(providerSlug)
24+
const model = getModelBySlug(providerSlug, modelSlug)
25+
26+
if (!provider || !model) {
27+
notFound()
28+
}
29+
30+
return createModelsOgImage({
31+
eyebrow: `${provider.name} model`,
32+
title: model.displayName,
33+
subtitle: `${provider.name} pricing, context window, and feature support generated from Sim's model registry.`,
34+
pills: [
35+
`Input ${formatPrice(model.pricing.input)}/1M`,
36+
`Output ${formatPrice(model.pricing.output)}/1M`,
37+
model.contextWindow ? `${formatTokenCount(model.contextWindow)} context` : 'Unknown context',
38+
model.capabilityTags[0] ?? 'Capabilities tracked',
39+
],
40+
domainLabel: `sim.ai${model.href}`,
41+
})
42+
}

0 commit comments

Comments
 (0)