snapshot of https://github.com/shadcn-ui/ui.git (main, 2026-09-15; blobs > 131072B omitted)
This commit is contained in:
@@ -0,0 +1,240 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import { useChat } from "@ai-sdk/react"
|
||||
|
||||
import { createChat, getMessageText } from "@/lib/ai"
|
||||
import {
|
||||
getReasoningLevelLabel,
|
||||
PartReasoning,
|
||||
} from "@/registry/bases/radix/blocks/preview-03/components/part-reasoning"
|
||||
import { PartText } from "@/registry/bases/radix/blocks/preview-03/components/part-text"
|
||||
import { PromptInput } from "@/registry/bases/radix/blocks/preview-03/components/prompt-input"
|
||||
import { Example } from "@/registry/bases/radix/components/example"
|
||||
import { Button } from "@/registry/bases/radix/ui/button"
|
||||
import {
|
||||
Card,
|
||||
CardAction,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardFooter,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "@/registry/bases/radix/ui/card"
|
||||
import {
|
||||
Marker,
|
||||
MarkerContent,
|
||||
MarkerIcon,
|
||||
} from "@/registry/bases/radix/ui/marker"
|
||||
import { Message, MessageContent } from "@/registry/bases/radix/ui/message"
|
||||
import {
|
||||
MessageScroller,
|
||||
MessageScrollerButton,
|
||||
MessageScrollerContent,
|
||||
MessageScrollerItem,
|
||||
MessageScrollerProvider,
|
||||
MessageScrollerViewport,
|
||||
} from "@/registry/bases/radix/ui/message-scroller"
|
||||
import { Spinner } from "@/registry/bases/radix/ui/spinner"
|
||||
import { IconPlaceholder } from "@/app/(app)/create/components/icon-placeholder"
|
||||
|
||||
const chat = createChat()
|
||||
.user("Explain why the dashboard feels slow even though the API is fast.")
|
||||
.sleep(1000)
|
||||
.assistant(({ writer }) => {
|
||||
writer.reasoning(
|
||||
`The user is separating perceived performance from backend latency. They likely measured API response times and found them acceptable, but users still complain about slowness.
|
||||
|
||||
I should avoid jumping straight to server-side optimizations and instead frame the answer around what people actually experience.`
|
||||
)
|
||||
writer.sleep(600)
|
||||
writer.reasoning(
|
||||
`Perceived slowness usually lives in the gap between user action and visible feedback:
|
||||
|
||||
- input latency before the request fires
|
||||
- loading state placement and timing
|
||||
- layout shifts when data arrives
|
||||
- whether optimistic UI gives immediate acknowledgment
|
||||
|
||||
For a dashboard, I should also consider chart re-renders, independent card fetches, and skeleton dimensions that do not match the final layout.`
|
||||
)
|
||||
writer.sleep(600)
|
||||
writer.reasoning(
|
||||
`Ranking likely bottlenecks for this scenario:
|
||||
|
||||
1. No immediate feedback on filter or refresh actions
|
||||
2. Skeleton cards that collapse or expand when data lands
|
||||
3. Waterfall requests where each widget fetches on its own
|
||||
4. Empty-state flashes because stale data is not shown while revalidating
|
||||
|
||||
The answer should stay practical and ordered by impact, not by how technical each fix sounds.`
|
||||
)
|
||||
writer.text(
|
||||
`The API can be fast while the interface still **feels** slow.
|
||||
|
||||
Start by checking:
|
||||
|
||||
- whether the button gives immediate feedback
|
||||
- whether skeletons match the final layout
|
||||
- whether any reflow happens after data arrives
|
||||
- whether the loading state appears near the content it replaces`
|
||||
)
|
||||
})
|
||||
.user("What should I fix first?")
|
||||
.sleep(1000)
|
||||
.assistant(({ writer }) => {
|
||||
writer.reasoning(
|
||||
`The user wants prioritization, not another diagnostic list.
|
||||
|
||||
Given the dashboard context, immediate feedback and layout stability usually beat micro-optimizations. I should recommend one fix with a clear way to measure success.`
|
||||
)
|
||||
writer.text(
|
||||
`Fix **immediate feedback** first.
|
||||
|
||||
When someone clicks "Refresh", the button should disable and show a spinner within one frame — before the network round trip. That single change often cuts perceived latency in half.
|
||||
|
||||
Measure it with a screen recording and count frames between click and visual response.`
|
||||
)
|
||||
})
|
||||
|
||||
const initialMessages = chat.get(0)
|
||||
const transport = chat.transport()
|
||||
|
||||
export function ReasoningChat() {
|
||||
const { status, messages, sendMessage, setMessages } = useChat({
|
||||
messages: initialMessages,
|
||||
transport,
|
||||
})
|
||||
const nextMessage = chat.next(messages)
|
||||
const isBusy = status === "submitted" || status === "streaming"
|
||||
|
||||
const onSubmit = (input: string) => {
|
||||
if (!input.trim()) {
|
||||
return
|
||||
}
|
||||
|
||||
if (nextMessage && input === getMessageText(nextMessage)) {
|
||||
void sendMessage(nextMessage)
|
||||
return
|
||||
}
|
||||
|
||||
void sendMessage({ text: input })
|
||||
}
|
||||
|
||||
return (
|
||||
<Example
|
||||
title="Reasoning"
|
||||
className="bg-transparent p-0"
|
||||
containerClassName="flex-1"
|
||||
>
|
||||
<Card className="max-h-140 min-h-0 flex-1">
|
||||
<CardHeader>
|
||||
<CardTitle>Reasoning trace</CardTitle>
|
||||
<CardDescription>
|
||||
Layered reasoning traces across multiple assistant turns.
|
||||
</CardDescription>
|
||||
<CardAction>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="icon"
|
||||
aria-label="Start over"
|
||||
onClick={() => setMessages(initialMessages)}
|
||||
>
|
||||
<IconPlaceholder
|
||||
lucide="PlusIcon"
|
||||
tabler="IconPlus"
|
||||
hugeicons="PlusSignIcon"
|
||||
phosphor="PlusIcon"
|
||||
remixicon="RiAddLine"
|
||||
/>
|
||||
</Button>
|
||||
</CardAction>
|
||||
</CardHeader>
|
||||
<CardContent className="flex-1 overflow-hidden p-0">
|
||||
<MessageScrollerProvider>
|
||||
<MessageScroller>
|
||||
<MessageScrollerViewport>
|
||||
<MessageScrollerContent className="p-(--card-spacing)">
|
||||
{messages.map((message) => (
|
||||
<MessageScrollerItem
|
||||
key={message.id}
|
||||
messageId={message.id}
|
||||
scrollAnchor={message.role === "user"}
|
||||
>
|
||||
<Message
|
||||
align={message.role === "user" ? "end" : "start"}
|
||||
data-role={message.role}
|
||||
>
|
||||
<MessageContent
|
||||
className={
|
||||
message.role === "user"
|
||||
? "flex-col items-end gap-2"
|
||||
: "flex-col items-start gap-2"
|
||||
}
|
||||
>
|
||||
{message.parts.map((part, index) => {
|
||||
const reasoningIndex =
|
||||
part.type === "reasoning"
|
||||
? message.parts
|
||||
.slice(0, index)
|
||||
.filter(
|
||||
(currentPart) =>
|
||||
currentPart.type === "reasoning"
|
||||
).length
|
||||
: 0
|
||||
|
||||
return (
|
||||
<React.Fragment key={`${message.id}-${index}`}>
|
||||
{part.type === "text" ? (
|
||||
<PartText
|
||||
part={part}
|
||||
variant={
|
||||
message.role === "user"
|
||||
? "bubble"
|
||||
: "default"
|
||||
}
|
||||
/>
|
||||
) : part.type === "reasoning" ? (
|
||||
<PartReasoning
|
||||
part={part}
|
||||
label={getReasoningLevelLabel(
|
||||
reasoningIndex
|
||||
)}
|
||||
/>
|
||||
) : null}
|
||||
</React.Fragment>
|
||||
)
|
||||
})}
|
||||
</MessageContent>
|
||||
</Message>
|
||||
</MessageScrollerItem>
|
||||
))}
|
||||
{status === "submitted" ? (
|
||||
<MessageScrollerItem scrollAnchor={false}>
|
||||
<Marker role="status">
|
||||
<MarkerIcon>
|
||||
<Spinner />
|
||||
</MarkerIcon>
|
||||
<MarkerContent>Thinking...</MarkerContent>
|
||||
</Marker>
|
||||
</MessageScrollerItem>
|
||||
) : null}
|
||||
</MessageScrollerContent>
|
||||
</MessageScrollerViewport>
|
||||
<MessageScrollerButton />
|
||||
</MessageScroller>
|
||||
</MessageScrollerProvider>
|
||||
</CardContent>
|
||||
<CardFooter>
|
||||
<PromptInput
|
||||
onSubmit={onSubmit}
|
||||
status={status}
|
||||
defaultValue={
|
||||
isBusy ? "" : nextMessage ? getMessageText(nextMessage) : ""
|
||||
}
|
||||
/>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
</Example>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user