Skip to content

Commit

Permalink
updated landing (#116)
Browse files Browse the repository at this point in the history
  • Loading branch information
skull8888888 authored Oct 31, 2024
1 parent d216cd1 commit b70881c
Show file tree
Hide file tree
Showing 7 changed files with 166 additions and 60 deletions.
20 changes: 20 additions & 0 deletions frontend/app/api/stars/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
export async function GET() {
try {
const response = await fetch('https://api.github.com/repos/lmnr-ai/lmnr', {
headers: {
'Authorization': `Bearer ${process.env.GITHUB_TOKEN}`,
},
});

if (!response.ok) {
throw new Error('Failed to fetch stars', { cause: await response.text() });
}

const data = await response.json();
return Response.json({ stars: data.stargazers_count });

} catch (error) {
console.error('Error fetching stars:', error);
return Response.json({ error: 'Failed to fetch stars' }, { status: 500 });
}
}
24 changes: 21 additions & 3 deletions frontend/components/landing/landing-header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import Link from 'next/link';
import Image from 'next/image';
import logo from '@/assets/logo/logo.svg';
import { useState } from 'react';
import { useState, useEffect } from 'react';
import { Menu, X, XCircle } from 'lucide-react';
import { Button } from '../ui/button';
import { cn } from '@/lib/utils';
Expand All @@ -14,6 +14,21 @@ interface LandingHeaderProps {

export default function LandingHeader({ hasSession }: LandingHeaderProps) {
const [isMenuOpen, setIsMenuOpen] = useState(false);
const [starCount, setStarCount] = useState<number | null>(null);

useEffect(() => {
const fetchStars = async () => {
try {
const response = await fetch('/api/stars');
const data = await response.json();
setStarCount(data.stars);
} catch (error) {
console.error('Failed to fetch star count:', error);
}
};
fetchStars();
}, []);

const menuItemStyle =
'text-sm md:text-base font-medium px-2 md:px-2 py-2 md:py-1 transition-colors w-full text-left whitespace-nowrap md:rounded-sm hover:bg-secondary';

Expand Down Expand Up @@ -45,14 +60,17 @@ export default function LandingHeader({ hasSession }: LandingHeaderProps) {
isMenuOpen ? '' : 'hidden'
)}
>
<Link href="https://docs.lmnr.ai" className={menuItemStyle}>
<Link href="https://docs.lmnr.ai" target="_blank" className={menuItemStyle}>
Docs
</Link>
<Link href="/pricing" className={menuItemStyle}>
Pricing
</Link>
<Link target="_blank" href="https://github.com/lmnr-ai/lmnr" className={menuItemStyle}>
GitHub
GitHub {starCount && `★ ${starCount}`}
</Link>
<Link target="_blank" href="https://discord.gg/nNFUUDAKub" className={menuItemStyle}>
Discord
</Link>
<Link target="_blank" href="https://cal.com/robert-lmnr/demo">
<Button
Expand Down
56 changes: 53 additions & 3 deletions frontend/components/landing/landing.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,19 +22,28 @@ import Footer from './footer';
import { Button } from '../ui/button';
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import CodeEditor from '../ui/code-editor';
import { useEffect, useState } from 'react';

export default function Landing() {
const [stars, setStars] = useState<number | null>(null);

useEffect(() => {
fetch('/api/stars')
.then(res => res.json())
.then(data => setStars(data.stars))
.catch(err => console.error('Failed to fetch GitHub stars:', err));
}, []);

const sections = [
{
id: 'traces',
title: 'Traces',
description: 'When you trace your LLM application, you get a clear picture of every step of execution and simultaneously collect invaluable data. You can use it to set up better evaluations, as dynamic few-shot examples, and for fine-tuning.',
codeExample: `from lmnr import Laminar as L, observe
codeExample: `from lmnr import Laminar, observe
# automatically instruments common
# LLM frameworks and libraries
L.initialize(project_api_key="...")
Laminar.initialize(project_api_key="...")
@observe() # annotate all functions you want to trace
def my_function():
Expand Down Expand Up @@ -105,7 +114,7 @@ evaluate(
height={20}
className="mr-2"
/>
Star us on GitHub
Star us on GitHub {stars && `★ ${stars}`}
<ArrowUpRight className="ml-2 h-4 w-4" />
</Button>
</Link>
Expand Down Expand Up @@ -207,6 +216,7 @@ evaluate(
<div className="flex flex-col w-full">
{section.codeExample && (
<CodeEditor
background="bg-black"
className="bg-black md:rounded-tl-lg md:rounded-br-lg border-white"
value={section.codeExample}
language="python"
Expand Down Expand Up @@ -254,6 +264,7 @@ evaluate(
<PromptChainsCard className="h-full" />
</div>
</div>
<SelfHostCard />
</div>
</div>
<Footer />
Expand Down Expand Up @@ -425,3 +436,42 @@ function PromptChainsCard({ className }: { className?: string }) {
</div>
);
}

function SelfHostCard() {
return (
<div className="bg-secondary/30 border rounded-lg shadow-md hover:shadow-lg transition-all duration-200 cursor-pointer flex flex-col relative overflow-hidden group">
<div className="absolute inset-0 opacity-0 group-hover:opacity-100 transition-opacity duration-200">
<Image
src={noise1}
alt=""
className="w-full h-full object-cover object-top"
/>
</div>
<Link
href="https://github.com/lmnr-ai/lmnr"
target="_blank"
className="flex flex-col h-full relative z-10"
>
<div className="p-6 flex-grow space-y-2">
<h3 className="text-xl font-medium group-hover:text-white transition-colors duration-200">Fully open-source</h3>
<p className="text-secondary-foreground/80 text-sm group-hover:text-white transition-colors duration-200">
Laminar is fully open-source and easy to self-host. Get started with just a few commands.
</p>
<CodeEditor
className="p-0 max-h-[70px]"
value={`git clone https://github.com/lmnr-ai/lmnr
cd lmnr
docker compose up -d`}
background="bg-transparent"
editable={false}
/>
<div className="flex">
<div className="flex items-center rounded-lg p-1 px-2 text-sm border border-white/20">
Learn about self-hosting <ArrowUpRight className="ml-2 h-4 w-4" />
</div>
</div>
</div>
</Link>
</div>
);
}
2 changes: 1 addition & 1 deletion frontend/components/landing/pricing-card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export default function PricingCard({
</div>
</div>
{subfeatures && subfeatures[index] && (
<div className="text-sm text-white/70 ml-9">
<div className="text-sm ml-9">
{subfeatures[index]}
</div>
)}
Expand Down
110 changes: 63 additions & 47 deletions frontend/components/landing/pricing.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,29 @@ import {
import { usePostHog } from 'posthog-js/react';
import Image from 'next/image';
import noise from '@/assets/landing/noise1.jpeg';
import { Slider } from "@/components/ui/slider";
import { useState } from "react";

export default function Pricing() {
const posthog = usePostHog();

const [spanCount, setSpanCount] = useState(50); // in thousands
const [teamMembers, setTeamMembers] = useState(1);

const calculateProPrice = () => {
const basePrice = 25;
const additionalSpansCost = spanCount > 50 ? Math.floor((spanCount - 50) / 100 * 25) : 0;
const additionalMembersCost = (teamMembers - 1) * 25;
return basePrice + additionalSpansCost + additionalMembersCost;
};

const handleQuestionClick = (question: string) => {
posthog?.capture('faq_question_clicked', { question });
};

return (
<div className="flex flex-col items-center mt-32 w-full h-full">
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-2 gap-8 md:p-16">
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-8 md:p-16">
<div className="p-8 border rounded-lg flex flex-col space-y-4">
<PricingCard
className="text-secondary-foreground"
Expand Down Expand Up @@ -52,11 +64,11 @@ export default function Pricing() {
className="w-full h-full object-cover object-top"
/>
</div>
<div className="bg-transparent h-full w-full rounded p-8 flex flex-col space-y-4 z-20">
<div className="bg-transparent h-full w-full rounded p-8 flex flex-col z-20">
<PricingCard
className="text-white z-20"
title="Pro"
price="$25 / month"
price={`$${calculateProPrice()} / month`}
features={[
'50k spans / month included',
'60 day data retention',
Expand All @@ -70,58 +82,62 @@ export default function Pricing() {
null
]}
/>
{/* <div className="text-secondary-foreground">$20 / month for each additional member</div> */}
<Link href="/projects" className="w-full z-20">
<Button
className="h-10 text-base bg-white/90 text-black hover:bg-white/70 w-full"
variant="outline"

>
Get started
</Button>
</Link>
<div className="space-y-4 z-20 flex flex-col">
<div className="space-y-2">
<div className="text-white">Spans per month {spanCount}k</div>
<Slider
defaultValue={[50]}
max={1000}
min={50}
step={50}
onValueChange={(value) => setSpanCount(value[0])}
/>
</div>
<div className="space-y-2">
<div className="text-white">Team members {teamMembers}</div>
<Slider
defaultValue={[1]}
max={10}
min={1}
step={1}
onValueChange={(value) => setTeamMembers(value[0])}
/>
</div>
<Link href="/projects" className="w-full z-20">
<Button
className="h-10 text-base bg-white/90 text-black hover:bg-white/70 w-full"
variant="outline"
>
Get started
</Button>
</Link>
</div>
</div>
</div>
{/* <div className="p-8 border-2 rounded flex flex-col space-y-4">
<div className="p-8 border rounded-lg flex flex-col space-y-4">
<PricingCard
className="text-secondary-foreground"
title="Enterprise"
description="Build cutting-edge products on Laminar"
price="Custom"
title="Team"
price="$300 / month"
features={[
"Custom",
"10 projects / workspace",
"1M pipeline runs / month",
"1000MB storage",
"90 day log retention",
"5 members per workspace",
'1M spans / month',
'180 day data retention',
'5 team members included',
'Private Slack channel'
]}
subfeatures={[
null,
null,
'then $25 / additional team member',
null
]}
/>
<div className="text-secondary-foreground">$20 / month for each additional member</div>
<a target="_blank" href="https://cal.com/skull8888888/30min">
<Button variant="secondary" className="w-full">
Book a demo
<Link href="/projects">
<Button variant="secondary" className="w-full h-10">
Get started
</Button>
</a>
</div> */}
{/* <div className="p-8 border-2 rounded flex flex-col space-y-4 flex-1">
<PricingCard
title="Enterprise"
description="Perfect for large-scale businesses"
price="Custom"
features={[
"Unlimited workspaces",
"Unlimited projects",
"Unlimited pipeline runs / month",
"Configurable storage size",
"Configurable number of collaborators",
"Configurable log retention period",
"Unlimited code generations per month",
]} />
<Button variant={'secondary'} className="w-full">
Contact us
</Button>
</div> */}
</Link>
</div>
</div>
<div className="w-full max-w-3xl mt-16 mb-32 px-4">
<h2 className="text-2xl font-bold mb-4 text-center">
Expand Down
8 changes: 5 additions & 3 deletions frontend/components/ui/code-editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ interface CodeEditorProps {
editable?: boolean;
onChange?: (value: string) => void;
placeholder?: string;
background?: string;
}

const myTheme = createTheme({
Expand All @@ -38,7 +39,8 @@ export default function CodeEditor({
editable = true,
onChange,
className,
placeholder
placeholder,
background
}: CodeEditorProps) {
const extensions = [
EditorView.lineWrapping,
Expand All @@ -61,10 +63,10 @@ export default function CodeEditor({
}

return (
<div className={cn('w-full h-full flex flex-col p-2 bg-card text-foreground', className)}>
<div className={cn('w-full h-full flex flex-col p-2 bg-card text-foreground', background, className)}>
<CodeMirror
placeholder={placeholder}
className="border-none bg-card"
className={cn('border-none bg-card', background)}
theme={myTheme}
extensions={extensions}
editable={editable}
Expand Down
6 changes: 3 additions & 3 deletions frontend/components/ui/slider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,10 @@ const Slider = React.forwardRef<
)}
{...props}
>
<SliderPrimitive.Track className="relative h-1.5 w-full grow overflow-hidden rounded-full bg-primary/20">
<SliderPrimitive.Range className="absolute h-full bg-primary" />
<SliderPrimitive.Track className="relative h-1.5 w-full grow overflow-hidden rounded-full bg-white/20">
<SliderPrimitive.Range className="absolute h-full bg-white" />
</SliderPrimitive.Track>
<SliderPrimitive.Thumb className="block h-4 w-4 rounded-full border border-primary/50 bg-background shadow transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50" />
<SliderPrimitive.Thumb className="block h-3 w-3 rounded-full border border-white/50 bg-white shadow transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50" />
</SliderPrimitive.Root>
));
Slider.displayName = SliderPrimitive.Root.displayName;
Expand Down

0 comments on commit b70881c

Please sign in to comment.