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

Add collapsible feature #51

Merged
merged 1 commit into from
Apr 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
15 changes: 11 additions & 4 deletions app/action.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,11 @@ async function submit(formData?: FormData, skip?: boolean) {
const aiState = getMutableAIState<typeof AI>()
const uiStream = createStreamableUI()
const isGenerating = createStreamableValue(true)
const isCollapsed = createStreamableValue(false)

const messages: ExperimentalMessage[] = aiState.get() as any
// Limit the number of messages to 14
messages.splice(0, Math.max(messages.length - 14, 0))
// Limit the number of messages to 10
messages.splice(0, Math.max(messages.length - 10, 0))
// Get the user input from the form data
const userInput = skip
? `{"action": "skip"}`
Expand Down Expand Up @@ -50,13 +51,17 @@ async function submit(formData?: FormData, skip?: boolean) {

uiStream.done()
isGenerating.done()
isCollapsed.done(false)
aiState.done([
...aiState.get(),
{ role: 'assistant', content: `inquiry: ${inquiry?.question}` }
])
return
}

// Set the collapsed state to true
isCollapsed.done(true)

// Generate the answer
let answer = ''
let errorOccurred = false
Expand Down Expand Up @@ -95,7 +100,8 @@ async function submit(formData?: FormData, skip?: boolean) {
return {
id: Date.now(),
isGenerating: isGenerating.value,
component: uiStream.value
component: uiStream.value,
isCollapsed: isCollapsed.value
}
}

Expand All @@ -110,7 +116,8 @@ const initialAIState: {
// The initial UI state that the client will keep track of, which contains the message IDs and their UI nodes.
const initialUIState: {
id: number
isGenerating: StreamableValue<boolean>
isGenerating?: StreamableValue<boolean>
isCollapsed?: StreamableValue<boolean>
component: React.ReactNode
}[] = []

Expand Down
Binary file modified bun.lockb
Binary file not shown.
19 changes: 15 additions & 4 deletions components/chat-messages.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,25 @@
import { useUIState } from 'ai/rsc'
import { StreamableValue, useUIState } from 'ai/rsc'
import type { AI } from '@/app/action'
import { CollapsibleMessage } from './collapsible-message'

export function ChatMessages() {
const [messages, setMessages] = useUIState<typeof AI>()

return (
<>
{messages.map((message: { id: number; component: React.ReactNode }) => (
<div key={message.id}>{message.component}</div>
))}
{messages.map(
(message: {
id: number
component: React.ReactNode
isCollapsed?: StreamableValue<boolean>
}) => (
<CollapsibleMessage
key={message.id}
message={message}
isLastMessage={message.id === messages[messages.length - 1].id}
/>
)
)}
</>
)
}
1 change: 0 additions & 1 deletion components/chat-panel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ export function ChatPanel() {
...currentMessages,
{
id: Date.now(),
isGenerating: false,
component: <UserMessage message={input} />
}
])
Expand Down
72 changes: 72 additions & 0 deletions components/collapsible-message.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import React, { useEffect, useState } from 'react'
import {
Collapsible,
CollapsibleTrigger,
CollapsibleContent
} from '@radix-ui/react-collapsible'
import { Button } from './ui/button'
import { ChevronDown } from 'lucide-react'
import { StreamableValue, useStreamableValue } from 'ai/rsc'
import { cn } from '@/lib/utils'
import { Separator } from './ui/separator'

interface CollapsibleMessageProps {
message: {
id: number
isCollapsed?: StreamableValue<boolean>
component: React.ReactNode
}
isLastMessage?: boolean
}

export const CollapsibleMessage: React.FC<CollapsibleMessageProps> = ({
message,
isLastMessage = false
}) => {
const [data] = useStreamableValue(message.isCollapsed)
const isCollapsed = data ?? false
const [open, setOpen] = useState(isLastMessage)

useEffect(() => {
setOpen(isLastMessage)
}, [isCollapsed, isLastMessage])

// if not collapsed, return the component
if (!isCollapsed) {
return message.component
}

return (
<Collapsible
open={open}
onOpenChange={value => {
setOpen(value)
}}
>
<CollapsibleTrigger asChild>
<div
className={cn(
'w-full flex justify-end',
!isCollapsed ? 'hidden' : ''
)}
>
<Button
variant="ghost"
size={'icon'}
className={cn('-mt-3 rounded-full')}
>
<ChevronDown
className={cn(
open ? 'rotate-180' : 'rotate-0',
'h-4 w-4 transition-all'
)}
/>
<span className="sr-only">collapse</span>
</Button>
</div>
</CollapsibleTrigger>
<CollapsibleContent>{message.component}</CollapsibleContent>
{!open && <Separator className="my-2 bg-muted" />}
</Collapsible>
)
}
1 change: 0 additions & 1 deletion components/search-related.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ export const SearchRelated: React.FC<SearchRelatedProps> = ({

const userMessage = {
id: Date.now(),
isGenerating: false,
component: <UserMessage message={query} isFirstMessage={false} />
}

Expand Down
11 changes: 11 additions & 0 deletions components/ui/collapsible.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
"use client"

import * as CollapsiblePrimitive from "@radix-ui/react-collapsible"

const Collapsible = CollapsiblePrimitive.Root

const CollapsibleTrigger = CollapsiblePrimitive.CollapsibleTrigger

const CollapsibleContent = CollapsiblePrimitive.CollapsibleContent

export { Collapsible, CollapsibleTrigger, CollapsibleContent }
2 changes: 1 addition & 1 deletion components/user-message.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export const UserMessage: React.FC<UserMessageProps> = ({
isFirstMessage
}) => {
return (
<div className={cn({ 'pt-4': !isFirstMessage })}>
<div className={cn({ 'mt-4': !isFirstMessage })}>
<div className="text-xl">{message}</div>
</div>
)
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
"@ai-sdk/openai": "^0.0.2",
"@radix-ui/react-avatar": "^1.0.4",
"@radix-ui/react-checkbox": "^1.0.4",
"@radix-ui/react-collapsible": "^1.0.3",
"@radix-ui/react-dialog": "^1.0.5",
"@radix-ui/react-dropdown-menu": "^2.0.6",
"@radix-ui/react-label": "^2.0.2",
Expand Down