Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update examples and improve stability #271

Merged
merged 1 commit into from
Mar 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions app/new/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { redirect } from 'next/navigation'

export default async function NewPage() {
redirect('/')
}
2 changes: 1 addition & 1 deletion app/share/[id]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ export default async function SharePage({ params }: SharePageProps) {
</div>
</div>
<AI>
<ChatList messages={uiState} />
<ChatList messages={uiState} isShared={true} />
</AI>
</div>
<FooterText className="py-8" />
Expand Down
22 changes: 21 additions & 1 deletion components/chat-list.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,37 @@
import { Separator } from '@/components/ui/separator'
import { UIState } from '@/lib/chat/actions'
import { Session } from '@/lib/types'
import Link from 'next/link'

export interface ChatList {
messages: UIState
session?: Session
isShared: boolean
}

export function ChatList({ messages }: ChatList) {
export function ChatList({ messages, session, isShared }: ChatList) {
if (!messages.length) {
return null
}

return (
<div className="relative mx-auto max-w-2xl px-4">
{!isShared && !session ? (
<div className="mb-8 rounded-lg border bg-white p-4 dark:bg-zinc-950">
<p className="text-muted-foreground leading-normal">
Please{' '}
<Link href="/login" className="underline">
log in
</Link>{' '}
or{' '}
<Link href="/signup" className="underline">
sign up
</Link>{' '}
to save and revisit your chat history!
</p>
</div>
) : null}

{messages.map((message, index) => (
<div key={message.id}>
{message.display}
Expand Down
24 changes: 12 additions & 12 deletions components/chat-panel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,24 +27,24 @@ export function ChatPanel({ id, title, input, setInput }: ChatPanelProps) {

const exampleMessages = [
{
heading: 'Explain the concept',
subheading: 'of a serverless function',
message: `Explain the concept of a serverless function`
heading: 'What are the',
subheading: 'trending memecoins today?',
message: `What are the trending memecoins today?`
},
{
heading: 'What are the benefits',
subheading: 'of using turborepo in my codebase?',
message: 'What are the benefits of using turborepo in my codebase?'
heading: 'What is the price of',
subheading: 'DOGE in the stock market?',
message: 'What is the price of DOGE in the stock market?'
},
{
heading: 'List differences between',
subheading: 'pages and app router in Next.js',
message: `List differences between pages and app router in Next.js`
heading: 'I would like to buy',
subheading: '42 DOGE coins',
message: `I would like to buy 42 DOGE coins`
},
{
heading: 'What is the price',
subheading: `of VRCL in the stock market?`,
message: `What is the price of VRCL in the stock market?`
heading: 'What are some',
subheading: `recent events about DOGE?`,
message: `What are some recent events about DOGE?`
}
]

Expand Down
2 changes: 1 addition & 1 deletion components/chat.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ export function Chat({ id, className, session, missingKeys }: ChatProps) {
<div className={cn('pb-[200px] pt-4 md:pt-10', className)}>
{messages.length ? (
<>
<ChatList messages={messages} />
<ChatList messages={messages} isShared={false} session={session} />
<ChatScrollAnchor trackVisibility={isLoading} />
</>
) : (
Expand Down
2 changes: 1 addition & 1 deletion components/empty-screen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ export function EmptyScreen({ setInput }: Pick<UseChatHelpers, 'setInput'>) {
<ExternalLink href="https://vercel.com/blog/ai-sdk-3-generative-ui">
React Server Components
</ExternalLink>{' '}
to combine text with UI generated as output of the LLM. The UI state
to combine text with generative UI as output of the LLM. The UI state
is synced through the SDK so the model is aware of your interactions
as they happen.
</p>
Expand Down
4 changes: 2 additions & 2 deletions components/footer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ export function FooterText({ className, ...props }: React.ComponentProps<'p'>) {
>
Open source AI chatbot built with{' '}
<ExternalLink href="https://nextjs.org">Next.js</ExternalLink> and{' '}
<ExternalLink href="https://vercel.com/storage/kv">
Vercel KV
<ExternalLink href="https://github.com/vercel/ai">
Vercel AI SDK
</ExternalLink>
.
</p>
Expand Down
2 changes: 1 addition & 1 deletion components/header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ async function UserOrLogin() {
<SidebarToggle />
</>
) : (
<Link href="/" rel="nofollow">
<Link href="/new" rel="nofollow">
<IconNextChat className="size-6 mr-2 dark:hidden" inverted />
<IconNextChat className="hidden size-6 mr-2 dark:block" />
</Link>
Expand Down
6 changes: 3 additions & 3 deletions components/prompt-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -72,9 +72,9 @@ export function PromptForm({
<Button
variant="outline"
size="icon"
className="absolute left-0 top-4 size-8 rounded-full bg-background p-0 sm:left-4"
className="absolute left-0 top-[14px] size-8 rounded-full bg-background p-0 sm:left-4"
onClick={() => {
router.push('/')
router.push('/new')
}}
>
<IconPlus />
Expand All @@ -98,7 +98,7 @@ export function PromptForm({
value={input}
onChange={e => setInput(e.target.value)}
/>
<div className="absolute right-0 top-4 sm:right-4">
<div className="absolute right-0 top-[13px] sm:right-4">
<Tooltip>
<TooltipTrigger asChild>
<Button type="submit" size="icon" disabled={input === ''}>
Expand Down
6 changes: 3 additions & 3 deletions components/stocks/events-skeleton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@ const placeholderEvents = [
}
]

export const EventsSkeleton = ({ events = placeholderEvents }) => {
export const EventsSkeleton = () => {
return (
<div className="-mt-2 flex flex-col gap-2 py-4">
<div className="-mt-2 flex w-full flex-col gap-2 py-4">
{placeholderEvents.map(event => (
<div
key={event.date}
className="max-w-96 flex gap-1 shrink-0 flex-col rounded-lg bg-zinc-800 p-4"
className="flex shrink-0 flex-col gap-1 rounded-lg bg-zinc-800 p-4"
>
<div className="w-fit rounded-md bg-zinc-700 text-sm text-transparent">
{event.date}
Expand Down
4 changes: 2 additions & 2 deletions components/stocks/event.tsx → components/stocks/events.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@ interface Event {

export function Events({ props: events }: { props: Event[] }) {
return (
<div className="-mt-2 flex flex-col gap-2">
<div className="-mt-2 flex w-full flex-col gap-2 py-4">
{events.map(event => (
<div
key={event.date}
className="max-w-96 flex shrink-0 flex-col rounded-lg bg-zinc-800 p-4 gap-1"
className="flex shrink-0 flex-col gap-1 rounded-lg bg-zinc-800 p-4"
>
<div className="text-sm text-zinc-400">
{format(parseISO(event.date), 'dd LLL, yyyy')}
Expand Down
6 changes: 2 additions & 4 deletions components/stocks/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,7 @@ const Purchase = dynamic(
{
ssr: false,
loading: () => (
<div className="rounded-lg bg-zinc-900 px-4 py-5 text-center text-xs">
Loading stock info...
</div>
<div className="h-[375px] rounded-xl border bg-zinc-950 p-4 text-green-400 sm:h-[314px]" />
)
}
)
Expand All @@ -30,7 +28,7 @@ const Stocks = dynamic(() => import('./stocks').then(mod => mod.Stocks), {
loading: () => <StocksSkeleton />
})

const Events = dynamic(() => import('./event').then(mod => mod.Events), {
const Events = dynamic(() => import('./events').then(mod => mod.Events), {
ssr: false,
loading: () => <EventsSkeleton />
})
Expand Down
22 changes: 11 additions & 11 deletions components/stocks/stock-purchase.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,18 @@ import { formatNumber } from '@/lib/utils'
import type { AI } from '@/lib/chat/actions'

interface Purchase {
defaultAmount?: number
name: string
numberOfShares?: number
symbol: string
price: number
status: 'requires_action' | 'completed' | 'expired'
}

export function Purchase({
props: { defaultAmount, name, price, status = 'expired' }
props: { numberOfShares, symbol, price, status = 'requires_action' }
}: {
props: Purchase
}) {
const [value, setValue] = useState(defaultAmount || 100)
const [value, setValue] = useState(numberOfShares || 100)
const [purchasingUI, setPurchasingUI] = useState<null | React.ReactNode>(null)
const [aiState, setAIState] = useAIState<typeof AI>()
const [, setMessages] = useUIState<typeof AI>()
Expand Down Expand Up @@ -60,11 +60,11 @@ export function Purchase({
}

return (
<div className="rounded-xl border bg-zinc-950 p-4 text-green-400 mt-2">
<div className="rounded-xl border bg-zinc-950 p-4 text-green-400">
<div className="float-right inline-block rounded-full bg-white/10 px-2 py-1 text-xs">
+1.23% ↑
</div>
<div className="text-lg text-zinc-300">{name}</div>
<div className="text-lg text-zinc-300">{symbol}</div>
<div className="text-3xl font-bold">${price}</div>
{purchasingUI ? (
<div className="mt-4 text-zinc-200">{purchasingUI}</div>
Expand Down Expand Up @@ -100,14 +100,14 @@ export function Purchase({
<div className="flex flex-wrap items-center text-xl font-bold sm:items-end sm:gap-2 sm:text-3xl">
<div className="flex basis-1/3 flex-col tabular-nums sm:basis-auto sm:flex-row sm:items-center sm:gap-2">
{value}
<span className="mb-1 text-sm font-normal text-zinc-600 dark:text-zinc-400 sm:mb-0">
<span className="mb-1 text-sm font-normal text-zinc-600 sm:mb-0 dark:text-zinc-400">
shares
</span>
</div>
<div className="basis-1/3 text-center sm:basis-auto">×</div>
<span className="flex basis-1/3 flex-col tabular-nums sm:basis-auto sm:flex-row sm:items-center sm:gap-2">
${price}
<span className="mb-1 ml-1 text-sm font-normal text-zinc-600 dark:text-zinc-400 sm:mb-0">
<span className="mb-1 ml-1 text-sm font-normal text-zinc-600 sm:mb-0 dark:text-zinc-400">
per share
</span>
</span>
Expand All @@ -118,9 +118,9 @@ export function Purchase({
</div>

<button
className="mt-6 w-full rounded-lg bg-green-500 px-4 py-2 text-zinc-900 dark:bg-green-500"
className="mt-6 w-full rounded-lg bg-green-500 px-4 py-2 font-bold text-zinc-900 hover:bg-green-600"
onClick={async () => {
const response = await confirmPurchase(name, price, value)
const response = await confirmPurchase(symbol, price, value)
setPurchasingUI(response.purchasingUI)

// Insert a new system message to the UI.
Expand All @@ -135,7 +135,7 @@ export function Purchase({
</>
) : status === 'completed' ? (
<p className="mb-2 text-white">
You have successfully purchased {value} ${name}. Total cost:{' '}
You have successfully purchased {value} ${symbol}. Total cost:{' '}
{formatNumber(value * price)}
</p>
) : status === 'expired' ? (
Expand Down
6 changes: 3 additions & 3 deletions components/stocks/stocks-skeleton.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
export const StocksSkeleton = () => {
return (
<div className="mb-4 flex flex-col gap-2 overflow-y-scroll pb-4 text-sm sm:flex-row">
<div className="flex h-[60px] w-full cursor-pointer flex-row gap-2 rounded-lg bg-zinc-900 p-2 text-left hover:bg-zinc-800 sm:w-[208px]"></div>
<div className="flex h-[60px] w-full cursor-pointer flex-row gap-2 rounded-lg bg-zinc-900 p-2 text-left hover:bg-zinc-800 sm:w-[208px]"></div>
<div className="flex h-[60px] w-full cursor-pointer flex-row gap-2 rounded-lg bg-zinc-900 p-2 text-left hover:bg-zinc-800 sm:w-[208px]"></div>
<div className="flex h-[60px] w-full cursor-pointer flex-row gap-2 rounded-lg bg-zinc-800 p-2 text-left hover:bg-zinc-800 sm:w-[208px]"></div>
<div className="flex h-[60px] w-full cursor-pointer flex-row gap-2 rounded-lg bg-zinc-800 p-2 text-left hover:bg-zinc-800 sm:w-[208px]"></div>
<div className="flex h-[60px] w-full cursor-pointer flex-row gap-2 rounded-lg bg-zinc-800 p-2 text-left hover:bg-zinc-800 sm:w-[208px]"></div>
</div>
)
}
74 changes: 41 additions & 33 deletions components/stocks/stocks.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,45 +15,53 @@ export function Stocks({ props: stocks }: { props: Stock[] }) {
const { submitUserMessage } = useActions()

return (
<div className="mb-4 flex flex-col gap-2 overflow-y-scroll pb-4 text-sm sm:flex-row">
{stocks.map(stock => (
<button
key={stock.symbol}
className="flex cursor-pointer flex-row gap-2 rounded-lg bg-zinc-800 p-2 text-left hover:bg-zinc-700 sm:w-52"
onClick={async () => {
const response = await submitUserMessage(`View ${stock.symbol}`)
setMessages(currentMessages => [...currentMessages, response])
}}
>
<div
className={`text-xl ${
stock.delta > 0 ? 'text-green-600' : 'text-red-600'
} flex w-11 flex-row justify-center rounded-md bg-white/10 p-2`}
<div>
<div className="mb-4 flex flex-col gap-2 overflow-y-scroll pb-4 text-sm sm:flex-row">
{stocks.map(stock => (
<button
key={stock.symbol}
className="flex cursor-pointer flex-row gap-2 rounded-lg bg-zinc-800 p-2 text-left hover:bg-zinc-700 sm:w-52"
onClick={async () => {
const response = await submitUserMessage(`View ${stock.symbol}`)
setMessages(currentMessages => [...currentMessages, response])
}}
>
{stock.delta > 0 ? '↑' : '↓'}
</div>
<div className="flex flex-col">
<div className="bold uppercase text-zinc-300">{stock.symbol}</div>
<div className="text-base text-zinc-500">${stock.price}</div>
</div>
<div className="ml-auto flex flex-col">
<div
className={`${
className={`text-xl ${
stock.delta > 0 ? 'text-green-600' : 'text-red-600'
} bold text-right uppercase`}
} flex w-11 flex-row justify-center rounded-md bg-white/10 p-2`}
>
{` ${((stock.delta / stock.price) * 100).toFixed(2)}%`}
{stock.delta > 0 ? '↑' : '↓'}
</div>
<div
className={`${
stock.delta > 0 ? 'text-green-700' : 'text-red-700'
} text-right text-base`}
>
{stock.delta}
<div className="flex flex-col">
<div className="bold uppercase text-zinc-300">{stock.symbol}</div>
<div className="text-base text-zinc-500">
${stock.price.toExponential(1)}
</div>
</div>
<div className="ml-auto flex flex-col">
<div
className={`${
stock.delta > 0 ? 'text-green-600' : 'text-red-600'
} bold text-right uppercase`}
>
{` ${((stock.delta / stock.price) * 100).toExponential(1)}%`}
</div>
<div
className={`${
stock.delta > 0 ? 'text-green-700' : 'text-red-700'
} text-right text-base`}
>
{stock.delta.toExponential(1)}
</div>
</div>
</div>
</button>
))}
</button>
))}
</div>
<div className="p-4 text-center text-sm text-zinc-500">
Note: Data and latency are simulated for illustrative purposes and
should not be considered as financial advice.
</div>
</div>
)
}
Loading