Skip to content

Commit eb9495a

Browse files
feat(site): publish the threads product page
1 parent 5f8aa48 commit eb9495a

6 files changed

Lines changed: 355 additions & 0 deletions

File tree

50.8 KB
Loading

apps/site/app/sitemap.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,12 @@ export default function sitemap(): MetadataRoute.Sitemap {
1616
changeFrequency: "daily",
1717
priority: 0.9,
1818
},
19+
{
20+
url: siteLinks.threads,
21+
lastModified: new Date("2026-07-10T00:00:00.000Z"),
22+
changeFrequency: "weekly",
23+
priority: 0.9,
24+
},
1925
{
2026
url: siteLinks.docsGettingStarted,
2127
lastModified: siteLastModified,

apps/site/app/threads/page.tsx

Lines changed: 346 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,346 @@
1+
import {
2+
ArrowRight,
3+
Braces,
4+
Check,
5+
CircleStop,
6+
GitBranch,
7+
GitFork,
8+
Layers3,
9+
Radio,
10+
RefreshCw,
11+
Route,
12+
Workflow,
13+
} from "lucide-react";
14+
import type { Metadata } from "next";
15+
16+
import { Footer } from "@/components/footer";
17+
import { Navbar } from "@/components/navbar";
18+
import {
19+
ThreadInstallCommand,
20+
ThreadPlayground,
21+
} from "@/components/thread-registry-showcase";
22+
import { siteLinks } from "@/lib/site-config";
23+
24+
export const metadata: Metadata = {
25+
title: "useThread — Branching Chats for AI SDK",
26+
description:
27+
"Keep the useChat interface and add message trees, branch navigation, and concurrent AI SDK response streams.",
28+
alternates: {
29+
canonical: siteLinks.threads,
30+
},
31+
openGraph: {
32+
url: siteLinks.threads,
33+
title: "useThread — Branching Chats for AI SDK",
34+
description:
35+
"A useChat-compatible active path backed by a complete message tree.",
36+
},
37+
};
38+
39+
const compatibility = [
40+
"messages",
41+
"sendMessage",
42+
"setMessages",
43+
"regenerate",
44+
"stop",
45+
"status + error",
46+
"tools + approvals",
47+
"ChatTransport",
48+
] as const;
49+
50+
const additions = [
51+
{
52+
icon: Route,
53+
title: "Cursor navigation",
54+
description:
55+
"Select any message and expose its root-to-node path as chat.messages.",
56+
},
57+
{
58+
icon: GitFork,
59+
title: "Message topology",
60+
description:
61+
"Read parents, children, siblings, leaves, and complete tree snapshots.",
62+
},
63+
{
64+
icon: Radio,
65+
title: "Independent runs",
66+
description:
67+
"Stream, stop, and resume responses without coupling them to the visible path.",
68+
},
69+
{
70+
icon: Layers3,
71+
title: "Parallel responses",
72+
description:
73+
"Start multiple assistant runs from one user message and follow only the one you choose.",
74+
},
75+
] as const;
76+
77+
const architectureRows = [
78+
{
79+
label: "Tree",
80+
detail: "Canonical messages, parent-child edges, and the selected cursor.",
81+
},
82+
{
83+
label: "Active path",
84+
detail: "A useChat-compatible projection from the root to the cursor.",
85+
},
86+
{
87+
label: "Branch runs",
88+
detail:
89+
"One isolated AI SDK request lifecycle and abort controller per response.",
90+
},
91+
{
92+
label: "Transport",
93+
detail:
94+
"Your existing AI SDK ChatTransport, shared without a new wire protocol.",
95+
},
96+
] as const;
97+
98+
export default function ThreadsPage() {
99+
return (
100+
<div className="flex min-h-screen flex-col">
101+
<Navbar />
102+
<main className="flex-1">
103+
<section className="border-border/50 border-b">
104+
<div className="mx-auto max-w-6xl px-6 pt-16 pb-14 sm:pt-24 sm:pb-20">
105+
<div className="grid gap-12 lg:grid-cols-[minmax(0,1.12fr)_minmax(23rem,0.88fr)] lg:items-end">
106+
<div className="min-w-0">
107+
<div className="flex items-center gap-2 font-mono text-muted-foreground text-xs uppercase tracking-[0.18em]">
108+
<GitBranch className="size-4" />
109+
useThread for AI SDK
110+
</div>
111+
<h1 className="mt-7 max-w-4xl font-display text-4xl leading-[0.98] tracking-tight sm:text-7xl">
112+
Branching conversations for AI SDK.
113+
</h1>
114+
<p className="mt-7 max-w-2xl text-foreground/72 text-lg leading-8 sm:text-xl">
115+
Keep the{" "}
116+
<code className="font-mono text-[0.9em] text-foreground">
117+
useChat
118+
</code>{" "}
119+
interface. Add a complete message tree, branch navigation, and
120+
concurrent responses that keep streaming when users move
121+
elsewhere.
122+
</p>
123+
<div className="mt-8 flex flex-wrap items-center gap-5">
124+
<a
125+
className="inline-flex min-h-11 items-center gap-2 bg-primary px-5 font-medium text-primary-foreground text-sm transition-opacity hover:opacity-85"
126+
href="#playground"
127+
>
128+
Try the playground
129+
<ArrowRight className="size-4" />
130+
</a>
131+
<a
132+
className="inline-flex min-h-11 items-center gap-2 border-border border-b text-foreground/75 text-sm transition-colors hover:text-foreground"
133+
href={`${siteLinks.github}/tree/main/packages/thread`}
134+
>
135+
Read the package
136+
<ArrowRight className="size-3.5" />
137+
</a>
138+
</div>
139+
</div>
140+
141+
<div className="min-w-0 border-border border-y bg-card">
142+
<div className="flex items-center justify-between border-border border-b px-4 py-3">
143+
<span className="font-mono text-muted-foreground text-xs">
144+
Chat.tsx
145+
</span>
146+
<span className="flex items-center gap-1.5 font-mono text-[10px] text-muted-foreground uppercase">
147+
<Check className="size-3" /> useChat compatible
148+
</span>
149+
</div>
150+
<pre className="overflow-x-auto px-4 py-5 font-mono text-[13px] leading-7">
151+
<code>
152+
<span className="text-muted-foreground">- </span>
153+
<span className="text-foreground/55">
154+
import {"{ useChat }"} from &quot;@ai-sdk/react&quot;;
155+
</span>
156+
{"\n"}
157+
<span className="text-foreground">
158+
+ import {"{ useThread }"} from
159+
&quot;@chatjs/thread/react&quot;;
160+
</span>
161+
{"\n\n"}
162+
<span className="text-muted-foreground">- </span>
163+
<span className="text-foreground/55">
164+
const chat = useChat({"{ transport }"});
165+
</span>
166+
{"\n"}
167+
<span className="text-foreground">
168+
+ const chat = useThread({"{ transport }"});
169+
</span>
170+
{"\n\n"}
171+
<span className="text-foreground">chat.messages;</span>
172+
<span className="text-muted-foreground">
173+
{" "}
174+
{"// selected path"}
175+
</span>
176+
{"\n"}
177+
<span className="text-foreground">chat.tree;</span>
178+
<span className="text-muted-foreground">
179+
{" "}
180+
{"// complete tree"}
181+
</span>
182+
</code>
183+
</pre>
184+
</div>
185+
</div>
186+
187+
<ThreadInstallCommand />
188+
</div>
189+
</section>
190+
191+
<section className="scroll-mt-20 py-16 sm:py-20" id="playground">
192+
<div className="mx-auto max-w-6xl px-6">
193+
<div className="mb-8 flex flex-col justify-between gap-5 sm:flex-row sm:items-end">
194+
<div className="max-w-2xl">
195+
<p className="font-mono text-muted-foreground text-xs uppercase tracking-[0.18em]">
196+
The package, not a mockup
197+
</p>
198+
<h2 className="mt-4 font-display text-3xl tracking-tight sm:text-5xl">
199+
Move through the tree while it streams.
200+
</h2>
201+
</div>
202+
<p className="max-w-sm text-foreground/65 text-sm leading-6">
203+
Branch from any message, request parallel replies, switch paths
204+
mid-stream, and stop individual runs. The canvas reads directly
205+
from useThread state.
206+
</p>
207+
</div>
208+
<ThreadPlayground />
209+
</div>
210+
</section>
211+
212+
<section className="border-border/50 border-y bg-card py-20 sm:py-28">
213+
<div className="mx-auto max-w-6xl px-6">
214+
<div className="grid gap-12 lg:grid-cols-[0.8fr_1.2fr] lg:gap-20">
215+
<div>
216+
<p className="font-mono text-muted-foreground text-xs uppercase tracking-[0.18em]">
217+
A strict extension
218+
</p>
219+
<h2 className="mt-5 font-display text-3xl tracking-tight sm:text-5xl">
220+
Your chat stays linear. Its history does not.
221+
</h2>
222+
<p className="mt-5 text-foreground/68 leading-7">
223+
Existing message lists and composers keep using the selected
224+
path. Tree-specific state is additive and namespaced under{" "}
225+
<code className="font-mono text-foreground text-sm">
226+
chat.tree
227+
</code>
228+
.
229+
</p>
230+
</div>
231+
232+
<div>
233+
<div className="flex items-center gap-2 border-border border-b pb-4">
234+
<Braces className="size-4 text-muted-foreground" />
235+
<h3 className="font-medium text-sm">Same AI SDK surface</h3>
236+
</div>
237+
<div className="grid grid-cols-2 border-border border-b sm:grid-cols-4">
238+
{compatibility.map((item) => (
239+
<div
240+
className="flex min-h-14 items-center gap-2 border-border border-r px-3 font-mono text-xs last:border-r-0 sm:[&:nth-child(4n)]:border-r-0"
241+
key={item}
242+
>
243+
<Check className="size-3.5 shrink-0 text-muted-foreground" />
244+
{item}
245+
</div>
246+
))}
247+
</div>
248+
249+
<div className="mt-10 grid sm:grid-cols-2">
250+
{additions.map((item) => (
251+
<article
252+
className="border-border border-b py-6 sm:even:border-l sm:even:pl-7 sm:odd:pr-7"
253+
key={item.title}
254+
>
255+
<item.icon className="size-4 text-muted-foreground" />
256+
<h3 className="mt-4 font-medium">{item.title}</h3>
257+
<p className="mt-2 text-foreground/65 text-sm leading-6">
258+
{item.description}
259+
</p>
260+
</article>
261+
))}
262+
</div>
263+
</div>
264+
</div>
265+
</div>
266+
</section>
267+
268+
<section className="py-20 sm:py-28">
269+
<div className="mx-auto max-w-6xl px-6">
270+
<div className="grid gap-14 lg:grid-cols-[0.92fr_1.08fr] lg:items-start lg:gap-20">
271+
<div>
272+
<p className="font-mono text-muted-foreground text-xs uppercase tracking-[0.18em]">
273+
Why it keeps working
274+
</p>
275+
<h2 className="mt-5 font-display text-3xl tracking-tight sm:text-5xl">
276+
One tree. One AI SDK lifecycle per response.
277+
</h2>
278+
<p className="mt-5 max-w-xl text-foreground/68 leading-7">
279+
A single linear chat engine cannot safely own several branch
280+
streams. useThread isolates each response while routing every
281+
update into its own assistant node once streaming begins.
282+
</p>
283+
<div className="mt-8 flex flex-wrap gap-x-6 gap-y-3 text-foreground/65 text-sm">
284+
<span className="flex items-center gap-2">
285+
<RefreshCw className="size-3.5" /> Resume per run
286+
</span>
287+
<span className="flex items-center gap-2">
288+
<CircleStop className="size-3.5" /> Stop per run
289+
</span>
290+
<span className="flex items-center gap-2">
291+
<Workflow className="size-3.5" /> Navigate independently
292+
</span>
293+
</div>
294+
</div>
295+
296+
<div className="border-border border-t">
297+
{architectureRows.map((row, index) => (
298+
<div
299+
className="grid gap-2 border-border border-b py-5 sm:grid-cols-[8rem_1fr] sm:gap-6"
300+
key={row.label}
301+
>
302+
<div className="flex items-center gap-3 font-mono text-xs">
303+
<span className="text-muted-foreground">
304+
0{index + 1}
305+
</span>
306+
{row.label}
307+
</div>
308+
<p className="text-foreground/68 text-sm leading-6">
309+
{row.detail}
310+
</p>
311+
</div>
312+
))}
313+
</div>
314+
</div>
315+
</div>
316+
</section>
317+
318+
<section className="border-border/50 border-y bg-card py-20 sm:py-24">
319+
<div className="mx-auto flex max-w-6xl flex-col items-start justify-between gap-10 px-6 lg:flex-row lg:items-end">
320+
<div className="max-w-2xl">
321+
<p className="font-mono text-muted-foreground text-xs uppercase tracking-[0.18em]">
322+
Headless by design
323+
</p>
324+
<h2 className="mt-5 font-display text-3xl tracking-tight sm:text-5xl">
325+
Own the experience. Choose how you integrate it.
326+
</h2>
327+
<p className="mt-5 text-foreground/68 leading-7">
328+
Install the versioned package or copy the source through the
329+
registry. Your conversation UI, branch controls, persistence,
330+
and server routes remain application-owned.
331+
</p>
332+
</div>
333+
<a
334+
className="inline-flex min-h-11 shrink-0 items-center gap-2 bg-primary px-5 font-medium text-primary-foreground text-sm transition-opacity hover:opacity-85"
335+
href={`${siteLinks.github}/blob/main/packages/thread/README.md`}
336+
>
337+
Read the integration guide
338+
<ArrowRight className="size-4" />
339+
</a>
340+
</div>
341+
</section>
342+
</main>
343+
<Footer />
344+
</div>
345+
);
346+
}

apps/site/components/footer.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import Link from "next/link";
33
import { siteLinks } from "@/lib/site-config";
44

55
const PRODUCT_LINKS = [
6+
{ label: "useThread", href: "/threads" },
67
{ label: "Demo", href: siteLinks.demo },
78
{ label: "Desktop App", href: siteLinks.desktop },
89
{ label: "Documentation", href: siteLinks.docs },

apps/site/components/navbar.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { siteLinks } from "@/lib/site-config";
44
import { ThemeToggle } from "./theme-toggle";
55

66
const NAV_LINKS = [
7+
{ label: "Threads", href: "/threads" },
78
{ label: "Demo", href: siteLinks.demo },
89
{ label: "Docs", href: siteLinks.docs },
910
{

apps/site/lib/site-config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ export const siteConfig = {
2424

2525
export const siteLinks = {
2626
home: siteConfig.url,
27+
threads: `${siteConfig.url}/threads`,
2728
docs: siteConfig.docsUrl,
2829
docsGettingStarted: `${siteConfig.docsUrl}/quickstart`,
2930
desktop: siteConfig.desktopUrl,

0 commit comments

Comments
 (0)