SPB Git forge

spb/polyllm

Public
15commits 1branches 0releases
2.2 MBsize
maindefault branch
13 days agolast push
TypeScript 97.4% SQL 1% JavaScript 0.9% CSS 0.6%
5.8 KB · 110 lines tsx
Raw Blame History
1"use client";2import * as React from "react";3import { useInView, useReducedMotion } from "motion/react";4import { BarChart3, Boxes, MessageSquare, RotateCcw, Swords } from "lucide-react";5import { Badge } from "@/components/ui/badge";6import { Segmented } from "@/components/ui/segmented";7import { cn } from "@/lib/utils";8import { Section, SectionHeading } from "./section";9import { Reveal } from "./reveal";10import { DesktopFrame, PhoneFrame } from "./frames";11import { fastestIndex, useFakeStreams, type FakeStream } from "./fake-stream";12import { DesktopArenaMock, DesktopChatMock, DesktopModelsMock, DesktopUsageMock, MobileArenaMock, MobileChatMock, MobileModelsMock, MobileUsageMock } from "./product-mock";13import { ARENA_PROMPT, ARENA_REPLIES, CHAT_PROMPT, CHAT_REPLY, MOCK_MODELS } from "./mock-data";1415type Tab = "chat" | "arena" | "models" | "usage";1617const CHAT_MODEL = MOCK_MODELS[0];18const CHAT_STREAMS: FakeStream[] = [{ text: CHAT_REPLY, speed: 4, delay: 700 }];1920const ARENA_MODELS = [MOCK_MODELS[1], MOCK_MODELS[2], MOCK_MODELS[3]];21const ARENA_TEXTS = ARENA_MODELS.map((m) => ARENA_REPLIES[m.key]);22const ARENA_STREAMS: FakeStream[] = [23  { text: ARENA_TEXTS[0], speed: 3, delay: 900 },24  { text: ARENA_TEXTS[1], speed: 5, delay: 350 },25  { text: ARENA_TEXTS[2], speed: 2, delay: 1300 },26];27const ARENA_FASTEST = fastestIndex(ARENA_STREAMS);2829const TAB_OPTIONS = [30  { value: "chat" as Tab, label: "Chat", icon: <MessageSquare /> },31  { value: "arena" as Tab, label: "Arena", icon: <Swords /> },32  { value: "models" as Tab, label: "Models", icon: <Boxes /> },33  { value: "usage" as Tab, label: "Usage", icon: <BarChart3 /> },34];3536const TAB_URL: Record<Tab, string> = { chat: "polyllm.io/app/chat", arena: "polyllm.io/app/arena", models: "polyllm.io/app/models", usage: "polyllm.io/app/usage" };37const TAB_NOTE: Record<Tab, string> = {38  chat: "Real streaming with a live metadata line: tokens, time to first token, throughput and cost.",39  arena: "One prompt, three models in parallel. On phones you swipe between answers; on desktop they sit side by side.",40  models: "The catalog reads the registry: context, prices, lifecycle and capability badges. Star favorites, select two to compare.",41  usage: "Usage is computed from real request records and the provider price sheets, with savings suggestions.",42};4344/** Chat and Arena streams live here so the desktop and phone frames stay in sync. */45function DemoScreens({ tab, active, replay }: { tab: Tab; active: boolean; replay: number }) {46  const reduce = !!useReducedMotion();47  const chat = useFakeStreams(CHAT_STREAMS, active && tab === "chat", replay, reduce);48  const arena = useFakeStreams(ARENA_STREAMS, active && tab === "arena", replay, reduce);4950  const chatProps = { prompt: CHAT_PROMPT, reply: CHAT_REPLY, progress: chat.progress[0], elapsedMs: chat.elapsedFor(0), ttftMs: chat.ttftFor(0), model: CHAT_MODEL };51  const arenaProps = { prompt: ARENA_PROMPT, models: ARENA_MODELS, replies: ARENA_TEXTS, progress: arena.progress, elapsedFor: arena.elapsedFor, ttftFor: arena.ttftFor, fastest: ARENA_FASTEST, allDone: arena.allDone, winner: 1 };5253  return (54    <div className="grid items-start gap-6 xl:grid-cols-[minmax(0,1fr)_300px]">55      {/* Desktop frame — never squeezed into the phone */}56      <div className="hidden min-w-0 md:block">57        <DesktopFrame url={TAB_URL[tab]}>58          {tab === "chat" ? <DesktopChatMock {...chatProps} /> : tab === "arena" ? <DesktopArenaMock {...arenaProps} /> : tab === "models" ? <DesktopModelsMock /> : <DesktopUsageMock />}59        </DesktopFrame>60      </div>61      {/* Phone frame — the real mobile layout */}62      <div className="md:hidden xl:block">63        <PhoneFrame label={`PolyLLM ${tab} on a phone`}>64          {tab === "chat" ? <MobileChatMock {...chatProps} /> : tab === "arena" ? <MobileArenaMock {...arenaProps} /> : tab === "models" ? <MobileModelsMock /> : <MobileUsageMock />}65        </PhoneFrame>66      </div>67    </div>68  );69}7071export function InteractiveDemo() {72  const ref = React.useRef<HTMLDivElement>(null);73  const inView = useInView(ref, { once: true, margin: "0px 0px -15% 0px" });74  const [tab, setTab] = React.useState<Tab>("chat");75  const [replay, setReplay] = React.useState(0);7677  const select = (t: Tab) => {78    setTab(t);79    setReplay((r) => r + 1);80  };8182  return (83    <Section id="demo" tone="subtle">84      <SectionHeading align="center" eyebrow="Interactive demo" title="See it work before you sign up" description="A hand-built preview of the workspace on desktop and on a phone. The text is scripted and streams locally in your browser; nothing is sent to any provider." />85      <Reveal delay={0.1} y={20} className="mt-10">86        <div ref={ref} className="mx-auto w-full max-w-6xl">87          <div className="mb-4 flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">88            <Segmented<Tab> value={tab} onChange={select} options={TAB_OPTIONS} ariaLabel="Demo view" className="w-full sm:w-auto" fill />89            <div className="flex items-center justify-between gap-2 sm:justify-end">90              <Badge variant="outline" className="sm:inline-flex">91                Product mock · scripted output92              </Badge>93              {tab === "chat" || tab === "arena" ? (94                <button type="button" onClick={() => setReplay((r) => r + 1)} className="tap inline-flex h-9 items-center gap-1.5 rounded-md border border-border bg-bg-elevated px-3 text-[13px] text-fg-muted transition-colors hover:text-fg">95                  <RotateCcw className="size-3.5" aria-hidden />96                  Replay97                </button>98              ) : null}99            </div>100          </div>101          <DemoScreens tab={tab} active={inView} replay={replay} />102          <p className={cn("mx-auto mt-4 max-w-2xl text-center text-[13px] leading-6 text-fg-muted")} aria-live="polite">103            {TAB_NOTE[tab]}104          </p>105        </div>106      </Reveal>107    </Section>108  );109}110