Skip to content

Commit 77f624b

Browse files
committed
Update docs
1 parent f036ea4 commit 77f624b

File tree

9 files changed

+134
-65
lines changed

9 files changed

+134
-65
lines changed

apps/website/app/api/chat/tools.ts

Lines changed: 2 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -46,14 +46,7 @@ const search_docs = (writer: UIMessageStreamWriter) =>
4646
log(`Found ${results.length} results`);
4747

4848
if (results.length === 0) {
49-
return {
50-
content: [
51-
{
52-
type: "text",
53-
text: `No documentation found for query: "${query}"`,
54-
},
55-
],
56-
};
49+
return `No documentation found for query: "${query}"`;
5750
}
5851

5952
log(`Processing ${results.length} results...`);
@@ -174,14 +167,7 @@ const get_doc_page = tool({
174167
const doc = pages.find((d) => d.url === slug || d.url.endsWith(slug));
175168

176169
if (!doc) {
177-
return {
178-
content: [
179-
{
180-
type: "text",
181-
text: `Documentation page not found: "${slug}"`,
182-
},
183-
],
184-
};
170+
return `Documentation page not found: "${slug}"`;
185171
}
186172

187173
return `# ${doc.data.title}\n\n${

apps/website/app/styles/geistdocs.css

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@
168168
}
169169

170170
.line-numbers code .line::before {
171-
@apply w-4 mr-6 inline-block text-right text-muted-foreground;
171+
@apply mr-6 inline-block w-6 text-right tabular-nums text-muted-foreground;
172172

173173
content: counter(step);
174174
counter-increment: step;

apps/website/components/geistdocs/chat.tsx

Lines changed: 80 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,17 @@ import { Tooltip, TooltipContent, TooltipTrigger } from "../ui/tooltip";
4646
import { CopyChat } from "./copy-chat";
4747
import { MessageMetadata } from "./message-metadata";
4848

49+
const isFromPreviousDay = (timestamp: number): boolean => {
50+
const messageDate = new Date(timestamp);
51+
const today = new Date();
52+
53+
return (
54+
messageDate.getFullYear() !== today.getFullYear() ||
55+
messageDate.getMonth() !== today.getMonth() ||
56+
messageDate.getDate() !== today.getDate()
57+
);
58+
};
59+
4960
export const useChatPersistence = () => {
5061
const saveTimeoutRef = useRef<ReturnType<typeof setTimeout> | undefined>(
5162
undefined
@@ -56,8 +67,23 @@ export const useChatPersistence = () => {
5667
db.messages.orderBy("sequence").toArray()
5768
);
5869

70+
// Clear messages if they're from a previous day
71+
useEffect(() => {
72+
if (storedMessages && storedMessages.length > 0) {
73+
const firstMessage = storedMessages[0];
74+
if (firstMessage && isFromPreviousDay(firstMessage.timestamp)) {
75+
db.messages.clear();
76+
}
77+
}
78+
}, [storedMessages]);
79+
80+
// Filter out stale messages from previous days
81+
const freshMessages = storedMessages?.filter(
82+
(msg) => !isFromPreviousDay(msg.timestamp)
83+
);
84+
5985
const initialMessages =
60-
storedMessages?.map(({ timestamp, sequence, ...message }) => message) ?? [];
86+
freshMessages?.map(({ timestamp, sequence, ...message }) => message) ?? [];
6187

6288
const isLoading = storedMessages === undefined;
6389

@@ -118,15 +144,20 @@ type ChatProps = {
118144
suggestions: string[];
119145
};
120146

121-
const ChatInner = ({ basePath, suggestions }: ChatProps) => {
147+
type ChatInnerProps = ChatProps & {
148+
isOpen: boolean;
149+
};
150+
151+
const ChatInner = ({ basePath, suggestions, isOpen }: ChatInnerProps) => {
152+
const textareaRef = useRef<HTMLTextAreaElement>(null);
122153
const [isInitialized, setIsInitialized] = useState(false);
123154
const [localPrompt, setLocalPrompt] = useState("");
124155
const [providerKey, setProviderKey] = useState(0);
125156
const { prompt, setPrompt, setIsOpen } = useChatContext();
126157
const { initialMessages, isLoading, saveMessages, clearMessages } =
127158
useChatPersistence();
128159

129-
const { messages, sendMessage, status, setMessages } = useChat({
160+
const { messages, sendMessage, status, setMessages, stop } = useChat({
130161
transport: new DefaultChatTransport({
131162
api: basePath ? `${basePath}/api/chat` : "/api/chat",
132163
}),
@@ -163,24 +194,42 @@ const ChatInner = ({ basePath, suggestions }: ChatProps) => {
163194
}
164195
}, [messages, saveMessages, isInitialized]);
165196

166-
const handleSuggestionClick = async (suggestion: string) => {
167-
await sendMessage({ text: suggestion });
197+
// Focus textarea when chat opens
198+
useEffect(() => {
199+
if (isOpen) {
200+
// Small delay to ensure the panel/drawer animation has started
201+
const timer = setTimeout(() => {
202+
textareaRef.current?.focus();
203+
}, 100);
204+
return () => clearTimeout(timer);
205+
}
206+
}, [isOpen]);
207+
208+
const handleSuggestionClick = async (text: string) => {
209+
if (status === "streaming" || status === "submitted") {
210+
return;
211+
}
168212
setLocalPrompt("");
169213
setPrompt("");
214+
await sendMessage({ text });
170215
};
171216

172217
const handleSubmit: PromptInputProps["onSubmit"] = async (message, event) => {
173218
event.preventDefault();
174219

220+
if (status === "streaming" || status === "submitted") {
221+
return;
222+
}
223+
175224
const { text } = message;
176225

177226
if (!text) {
178227
return;
179228
}
180229

181-
await sendMessage({ text });
182230
setLocalPrompt("");
183231
setPrompt("");
232+
await sendMessage({ text });
184233
};
185234

186235
const handleClearChat = async () => {
@@ -249,7 +298,7 @@ const ChatInner = ({ basePath, suggestions }: ChatProps) => {
249298
key={message.id}
250299
>
251300
<MessageMetadata
252-
inProgress={status === "submitted"}
301+
inProgress={status === "submitted" || status === "streaming"}
253302
parts={message.parts as MyUIMessage["parts"]}
254303
/>
255304
{message.parts
@@ -260,7 +309,8 @@ const ChatInner = ({ basePath, suggestions }: ChatProps) => {
260309
className="text-wrap"
261310
rehypePlugins={[
262311
defaultRehypePlugins.raw,
263-
[
312+
defaultRehypePlugins.katex,
313+
[
264314
harden,
265315
{
266316
defaultOrigin:
@@ -320,13 +370,24 @@ const ChatInner = ({ basePath, suggestions }: ChatProps) => {
320370
setLocalPrompt(e.target.value);
321371
setPrompt(e.target.value);
322372
}}
373+
ref={textareaRef}
323374
/>
324375
</PromptInputBody>
325376
<PromptInputFooter>
326377
<p className="text-muted-foreground text-xs">
327378
{localPrompt.length} / 1000
328379
</p>
329-
<PromptInputSubmit status={status} />
380+
<PromptInputSubmit
381+
onClick={
382+
status === "streaming"
383+
? (e) => {
384+
e.preventDefault();
385+
stop();
386+
}
387+
: undefined
388+
}
389+
status={status}
390+
/>
330391
</PromptInputFooter>
331392
</PromptInput>
332393
</PromptInputProvider>
@@ -382,7 +443,11 @@ export const Chat = ({ basePath, suggestions }: ChatProps) => {
382443
)}
383444
data-state={isOpen ? "open" : "closed"}
384445
>
385-
<ChatInner basePath={basePath} suggestions={suggestions} />
446+
<ChatInner
447+
basePath={basePath}
448+
isOpen={isOpen}
449+
suggestions={suggestions}
450+
/>
386451
</div>
387452
</Portal.Root>
388453
<div className="md:hidden">
@@ -397,7 +462,11 @@ export const Chat = ({ basePath, suggestions }: ChatProps) => {
397462
</Button>
398463
</DrawerTrigger>
399464
<DrawerContent className="h-[80dvh]">
400-
<ChatInner basePath={basePath} suggestions={suggestions} />
465+
<ChatInner
466+
basePath={basePath}
467+
isOpen={isOpen}
468+
suggestions={suggestions}
469+
/>
401470
</DrawerContent>
402471
</Drawer>
403472
</div>

apps/website/components/geistdocs/code-block.tsx

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ type CodeBlockProps = {
2020
style?: CSSProperties;
2121
tabIndex?: number;
2222
title?: string;
23+
"data-line-numbers"?: string;
24+
"data-line-highlighting"?: string;
2325
};
2426

2527
export const CodeBlock = ({
@@ -29,9 +31,11 @@ export const CodeBlock = ({
2931
style,
3032
tabIndex,
3133
title,
34+
...rest
3235
}: CodeBlockProps) => {
3336
const ref = useRef<HTMLPreElement>(null);
3437
const [isCopied, setIsCopied] = useState(false);
38+
const { "data-line-numbers": lineNumbers } = rest;
3539

3640
const copyToClipboard = useCallback(async () => {
3741
if (typeof window === "undefined" || !navigator?.clipboard?.writeText) {
@@ -64,7 +68,7 @@ export const CodeBlock = ({
6468
<pre
6569
className={cn(
6670
"not-prose flex-1 overflow-x-auto rounded-sm border bg-background py-3 text-sm outline-none",
67-
"[&>code]:grid",
71+
"[&>code]:grid [&>code]:min-w-max",
6872
className,
6973
props.className
7074
)}
@@ -81,7 +85,7 @@ export const CodeBlock = ({
8185
if (!title) {
8286
return (
8387
<div className="relative mb-6">
84-
<CodeBlockComponent />
88+
<CodeBlockComponent className={cn(lineNumbers ? "line-numbers" : "")} />
8589
<Button
8690
className={cn(
8791
"absolute top-[5px] right-[5px] bg-background/80 backdrop-blur-sm",
@@ -100,13 +104,11 @@ export const CodeBlock = ({
100104
return (
101105
<Card className="not-prose mb-6 gap-0 overflow-hidden rounded-sm p-0 shadow-none">
102106
<CardHeader className="flex items-center gap-2 border-b bg-sidebar py-1.5! pr-1.5 pl-4 text-muted-foreground">
103-
{icon && (
104-
<div
105-
className="size-3.5 shrink-0"
106-
// biome-ignore lint/security/noDangerouslySetInnerHtml: "Required for icon prop."
107-
dangerouslySetInnerHTML={{ __html: icon as unknown as TrustedHTML }}
108-
/>
109-
)}
107+
<div
108+
className="size-3.5 shrink-0"
109+
// biome-ignore lint/security/noDangerouslySetInnerHtml: "Required for icon prop."
110+
dangerouslySetInnerHTML={{ __html: icon as unknown as TrustedHTML }}
111+
/>
110112
<CardTitle className="flex-1 font-mono font-normal text-sm tracking-tight">
111113
{title}
112114
</CardTitle>
@@ -120,7 +122,12 @@ export const CodeBlock = ({
120122
</Button>
121123
</CardHeader>
122124
<CardContent className="p-0">
123-
<CodeBlockComponent className="line-numbers rounded-none border-none" />
125+
<CodeBlockComponent
126+
className={cn(
127+
"rounded-none border-none",
128+
lineNumbers ? "line-numbers" : ""
129+
)}
130+
/>
124131
</CardContent>
125132
</Card>
126133
);

apps/website/components/geistdocs/footer.tsx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,12 @@ export const Footer = ({
1212
copyright = `Copyright Vercel ${new Date().getFullYear()}. All rights reserved.`,
1313
}: FooterProps) => (
1414
<footer className="border-t px-4 py-5 md:px-6">
15-
<div className="mx-auto flex items-center justify-between gap-4">
15+
<div className="mx-auto flex flex-col items-center justify-between gap-4 sm:flex-row">
1616
<div className="flex items-center gap-2">
17-
<SiVercel className="size-4" />
18-
<p className="text-center text-muted-foreground text-sm">{copyright}</p>
17+
<SiVercel className="size-4 shrink-0" />
18+
<p className="text-center text-muted-foreground text-sm sm:text-left">
19+
{copyright}
20+
</p>
1921
</div>
2022
<div className="flex items-center gap-2">
2123
<LanguageSelector />

apps/website/components/geistdocs/mdx-components.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ export const getMDXComponents = (
2222
components?: MDXComponents
2323
): MDXComponents => ({
2424
...defaultMdxComponents,
25-
...components,
2625

2726
pre: CodeBlock,
2827

@@ -56,4 +55,7 @@ export const getMDXComponents = (
5655
Mermaid,
5756

5857
Video,
58+
59+
// User components last to allow overwriting defaults
60+
...components,
5961
});

apps/website/components/geistdocs/message-metadata.tsx

Lines changed: 7 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import {
88
SourcesContent,
99
SourcesTrigger,
1010
} from "../ai-elements/sources";
11-
import { Spinner } from "../ui/spinner";
1211

1312
type MessageMetadataProps = {
1413
parts: MyUIMessage["parts"];
@@ -24,18 +23,12 @@ export const MessageMetadata = ({
2423
.filter((part) => part.type === "text" || isToolUIPart(part))
2524
.at(-1);
2625

27-
const reasoning = parts.at(-1)?.type === "reasoning";
28-
2926
if (!lastPart) {
30-
return (
31-
<div className="flex items-center gap-2">
32-
<Spinner />{" "}
33-
{reasoning ? <Shimmer className="text-xs">Thinking...</Shimmer> : ""}
34-
</div>
35-
);
27+
return <Shimmer className="text-xs">Thinking...</Shimmer>;
3628
}
3729

3830
const tool = isToolUIPart(lastPart) ? lastPart : null;
31+
const hasTextPart = parts.some((part) => part.type === "text");
3932

4033
const sources = Array.from(
4134
new Map(
@@ -45,6 +38,11 @@ export const MessageMetadata = ({
4538
).values()
4639
);
4740

41+
// Show loading state when sources exist but text hasn't arrived yet
42+
if (sources.length > 0 && !hasTextPart && inProgress) {
43+
return <Shimmer className="text-xs">Searching sources...</Shimmer>;
44+
}
45+
4846
if (sources.length > 0 && !(tool && inProgress)) {
4947
return (
5048
<Sources>
@@ -67,15 +65,6 @@ export const MessageMetadata = ({
6765
);
6866
}
6967

70-
if (tool && inProgress) {
71-
return (
72-
<div className="flex items-center gap-2">
73-
<Spinner />
74-
<Shimmer>{tool.type}</Shimmer>
75-
</div>
76-
);
77-
}
78-
7968
if (!tool && sources.length === 0) {
8069
return null;
8170
}

0 commit comments

Comments
 (0)