Skip to content

Commit f443c65

Browse files
Bran18Brandon fernandez
andauthored
[masterbots.ai] refactor: useScroll hook (#376)
* chore: refactor useScroll * chore: refactor useScroll * chore: refactor useScroll * Delete apps/masterbots.ai/lib/context/thread-context.tsx * chore: test commit * fix: import type * chore: add clearTimeout * chore: add destructuring * feat: update use-mb-scroll * feat: update thread component --------- Co-authored-by: Brandon fernandez <brandonfdez@Brandons-MacBook-Air.local>
1 parent 283bba6 commit f443c65

File tree

13 files changed

+434
-255
lines changed

13 files changed

+434
-255
lines changed

apps/masterbots.ai/app/api/payment/intent/route.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { NextRequest } from 'next/server';
1+
import type { NextRequest } from 'next/server';
22
import Stripe from 'stripe';
33

44
// Initialize Stripe with your secret key

apps/masterbots.ai/components/routes/chat/chat-accordion.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,11 @@ export const ChatAccordion = ({
2323
}: {
2424
className?: string
2525
children: React.ReactNode[]
26+
27+
2628
defaultState?: boolean
29+
30+
2731
triggerClass?: string
2832
contentClass?: string
2933
onToggle?: (isOpen: boolean) => void

apps/masterbots.ai/components/routes/chat/chat-list.tsx

Lines changed: 101 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { ChatMessage } from '@/components/routes/chat/chat-message'
44
import { SharedAccordion } from '@/components/shared/shared-accordion'
55
import { ShortMessage } from '@/components/shared/short-message'
66
import { Separator } from '@/components/ui/separator'
7-
import { useScroll } from '@/lib/hooks/use-scroll'
7+
import { useMBScroll } from '@/lib/hooks/use-mb-scroll'
88
import { useThread } from '@/lib/hooks/use-thread'
99
import { cn, createMessagePairs } from '@/lib/utils'
1010
import type { Message } from 'ai'
@@ -22,7 +22,6 @@ export interface ChatList {
2222
chatTitleClass?: string
2323
chatArrowClass?: string
2424
containerRef?: React.RefObject<HTMLDivElement>
25-
isNearBottom?: boolean
2625
isLoadingMessages?: boolean
2726
sendMessageFn?: (message: string) => void
2827
}
@@ -40,33 +39,42 @@ export function ChatList({
4039
chatContentClass,
4140
chatTitleClass,
4241
chatArrowClass,
43-
containerRef,
44-
sendMessageFn,
45-
isNearBottom,
42+
containerRef: externalContainerRef,
43+
sendMessageFn
4644
}: ChatList) {
4745
const [pairs, setPairs] = React.useState<MessagePair[]>([])
48-
const [previousConversationPairs, setPreviousConversationPairs] = React.useState<MessagePair[]>([])
46+
const [previousConversationPairs, setPreviousConversationPairs] =
47+
React.useState<MessagePair[]>([])
4948
const { isNewResponse, activeThread } = useThread()
50-
const localContainerRef = useRef<HTMLDivElement>(null)
51-
const effectiveContainerRef = containerRef || localContainerRef
52-
const chatMessages = (messages || activeThread?.messages || [])
53-
.sort((a, b) => new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime())
54-
const previousChatMessages = (activeThread?.thread?.messages || [])
55-
.sort((a, b) => new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime())
49+
const chatListRef = useRef<HTMLDivElement>(null)
50+
const messageContainerRef = useRef<HTMLDivElement>(null)
5651

57-
useScroll({
52+
//? Uses the external ref if provided, otherwise it uses our internal refs
53+
const effectiveContainerRef = externalContainerRef || chatListRef
54+
const effectiveThreadRef = messageContainerRef
55+
56+
const chatMessages = (messages || activeThread?.messages || []).sort(
57+
(a, b) => new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime()
58+
)
59+
const previousChatMessages = (activeThread?.thread?.messages || []).sort(
60+
(a, b) => new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime()
61+
)
62+
63+
const { isNearBottom } = useMBScroll({
5864
containerRef: effectiveContainerRef,
59-
threadRef: effectiveContainerRef,
65+
threadRef: effectiveThreadRef,
6066
isNewContent: isNewResponse,
6167
hasMore: false,
6268
isLast: true,
6369
loading: isLoadingMessages,
64-
loadMore: () => { }
70+
loadMore: () => {}
6571
})
6672

6773
useEffect(() => {
6874
if (chatMessages?.length) {
69-
const prePairs: MessagePair[] = createMessagePairs(chatMessages) as MessagePair[]
75+
const prePairs: MessagePair[] = createMessagePairs(
76+
chatMessages
77+
) as MessagePair[]
7078
setPairs(prevPairs => {
7179
if (!isEqual(prevPairs, prePairs)) {
7280
return prePairs
@@ -76,10 +84,12 @@ export function ChatList({
7684
}
7785
}, [chatMessages])
7886

79-
// biome-ignore lint/correctness/useExhaustiveDependencies: adding functions to array dep is not needed
87+
// biome-ignore lint/correctness/useExhaustiveDependencies: <explanation>
8088
useEffect(() => {
8189
if (previousChatMessages?.length) {
82-
const prePairs: MessagePair[] = createMessagePairs(previousChatMessages) as MessagePair[]
90+
const prePairs: MessagePair[] = createMessagePairs(
91+
previousChatMessages
92+
) as MessagePair[]
8393
setPreviousConversationPairs(prevPairs => {
8494
if (!isEqual(prevPairs, prePairs)) {
8595
return prePairs
@@ -98,20 +108,22 @@ export function ChatList({
98108
<div
99109
ref={effectiveContainerRef}
100110
className={cn(
101-
'relative max-w-3xl px-4 mx-auto',
111+
'relative max-w-3xl px-4 mx-auto overflow-auto scrollbar-thin',
102112
className,
103113
{ 'flex flex-col gap-3': isThread }
104114
)}
105115
>
106-
<MessagePairs
107-
pairs={pairs}
108-
previousPairs={previousConversationPairs}
109-
isThread={isThread}
110-
chatTitleClass={chatTitleClass}
111-
chatArrowClass={chatArrowClass}
112-
chatContentClass={chatContentClass}
113-
sendMessageFn={sendMessageFn}
114-
/>
116+
<div ref={effectiveThreadRef} className="min-h-full">
117+
<MessagePairs
118+
pairs={pairs}
119+
previousPairs={previousConversationPairs}
120+
isThread={isThread}
121+
chatTitleClass={chatTitleClass}
122+
chatArrowClass={chatArrowClass}
123+
chatContentClass={chatContentClass}
124+
sendMessageFn={sendMessageFn}
125+
/>
126+
</div>
115127
</div>
116128
)
117129
}
@@ -123,7 +135,7 @@ function MessagePairs({
123135
chatTitleClass,
124136
chatArrowClass,
125137
chatContentClass,
126-
sendMessageFn,
138+
sendMessageFn
127139
}: {
128140
pairs: MessagePair[]
129141
previousPairs: MessagePair[]
@@ -153,8 +165,8 @@ function MessagePairs({
153165
sendMessageFn={sendMessageFn}
154166
/>
155167
))}
156-
{(previousPairs.length > 0 && pairs.length > 0) && (
157-
<Separator className="relative mt-6 -bottom-1.5 h-1.5 z-[2] rounded-sm bg-iron dark:bg-mirage"/>
168+
{previousPairs.length > 0 && pairs.length > 0 && (
169+
<Separator className="relative mt-6 -bottom-1.5 h-1.5 z-[2] rounded-sm bg-iron dark:bg-mirage" />
158170
)}
159171
{pairs.map((pair: MessagePair, key: number, pairsArray) => (
160172
<MessagePairAccordion
@@ -174,7 +186,15 @@ function MessagePairs({
174186
)
175187
}
176188

177-
export function MessagePairAccordion({ pair, isThread, index, arrayLength, isNewResponse, type, ...props }: {
189+
export function MessagePairAccordion({
190+
pair,
191+
isThread,
192+
index,
193+
arrayLength,
194+
isNewResponse,
195+
type,
196+
...props
197+
}: {
178198
pair: MessagePair
179199
isThread: boolean
180200
index: number
@@ -191,20 +211,27 @@ export function MessagePairAccordion({ pair, isThread, index, arrayLength, isNew
191211
return (
192212
<SharedAccordion
193213
key={`${pair.userMessage.createdAt}-${pair.chatGptMessage[0]?.id ?? 'pending'}`}
194-
defaultState={index === 0 || index === arrayLength - 1 || (index === arrayLength - 2 && isNewResponse)}
214+
defaultState={
215+
index === 0 ||
216+
index === arrayLength - 1 ||
217+
(index === arrayLength - 2 && isNewResponse)
218+
}
195219
className={cn(
196220
{ relative: isThread },
197-
// Add subtle background tint and left border for previous messages
198-
isPrevious && 'bg-accent/25 rounded-[8px] border-l-accent/20',
221+
// Adds subtle background tint and left border for previous messages
222+
isPrevious && 'bg-accent/25 rounded-[8px] border-l-accent/20'
199223
)}
200224
triggerClass={cn(
201225
'py-[0.4375rem]',
202226
{
203-
'sticky top-0 md:-top-10 z-[1] px-3 [&[data-state=open]]:rounded-t-[8px]': isThread,
227+
'sticky top-0 md:-top-10 z-[1] px-3 [&[data-state=open]]:rounded-t-[8px]':
228+
isThread,
204229
'px-[calc(32px-0.25rem)]': !isThread,
205-
'hidden': !isThread && index === 0,// Style differences for previous vs current messages
206-
'dark:bg-[#1d283a9a] bg-iron !border-l-[transparent] [&[data-state=open]]:!bg-gray-400/50 dark:[&[data-state=open]]:!bg-mirage': !isPrevious,
207-
'bg-accent/15 dark:bg-accent/15 hover:bg-accent/30 hover:dark:bg-accent/30 border-l-accent/20 dark:border-l-accent/20 [&[data-state=open]]:!bg-accent/25 dark:[&[data-state=open]]:!bg-accent/25': isPrevious,
230+
hidden: !isThread && index === 0, // Style differences for previous vs current messages
231+
'dark:bg-[#1d283a9a] bg-iron !border-l-[transparent] [&[data-state=open]]:!bg-gray-400/50 dark:[&[data-state=open]]:!bg-mirage':
232+
!isPrevious,
233+
'bg-accent/15 dark:bg-accent/15 hover:bg-accent/30 hover:dark:bg-accent/30 border-l-accent/20 dark:border-l-accent/20 [&[data-state=open]]:!bg-accent/25 dark:[&[data-state=open]]:!bg-accent/25':
234+
isPrevious
208235
},
209236
props.chatTitleClass
210237
)}
@@ -219,14 +246,13 @@ export function MessagePairAccordion({ pair, isThread, index, arrayLength, isNew
219246
variant="chat"
220247
>
221248
{/* Thread Title with indicator for previous messages */}
222-
{!isThread && index === 0 ? '' : (
249+
{!isThread && index === 0 ? (
250+
''
251+
) : (
223252
<div
224-
className={cn(
225-
'flex items-start gap-2',
226-
{
227-
'[&_div]:text-sm': isPrevious,
228-
}
229-
)}
253+
className={cn('flex items-start gap-2', {
254+
'[&_div]:text-sm': isPrevious
255+
})}
230256
>
231257
<ChatMessage actionRequired={false} message={pair.userMessage} />
232258
</div>
@@ -238,17 +264,21 @@ export function MessagePairAccordion({ pair, isThread, index, arrayLength, isNew
238264
<div className="flex-1 px-1 space-y-2 overflow-hidden text-left">
239265
<ShortMessage content={pair.chatGptMessage[0]?.content} />
240266
</div>
241-
) : ''}
267+
) : (
268+
''
269+
)}
242270
</div>
243-
) : <></>}
271+
) : (
272+
<></>
273+
)}
244274

245275
{/* Thread Content */}
246276
<div
247277
className={cn(
248278
'mx-4 md:mx-[46px] px-1 py-4 border-transparent dark:border-x-mirage border-x-gray-300 border h-full',
249-
{
279+
{
250280
'!border-[transparent]': !isThread && index === 0,
251-
'[&>div>div>div_*]:!text-xs': isPrevious,
281+
'[&>div>div>div_*]:!text-xs': isPrevious
252282
},
253283
props.chatContentClass
254284
)}
@@ -258,26 +288,32 @@ export function MessagePairAccordion({ pair, isThread, index, arrayLength, isNew
258288
<span className="absolute top-1 -left-5 px-1.5 py-0.5 text-[10px] font-medium rounded-md bg-accent text-accent-foreground">
259289
Previous Thread
260290
</span>
261-
<div className="opacity-50 pb-3 overflow-hidden text-sm mt-4">
262-
Continued from <b>&ldquo;{pair.userMessage.content.trim()}&rdquo;</b> thread{activeThread?.thread?.user?.username ? `, by ${activeThread?.thread?.user?.username}.` : '.'}
291+
<div className="pb-3 mt-4 overflow-hidden text-sm opacity-50">
292+
Continued from{' '}
293+
<b>&ldquo;{pair.userMessage.content.trim()}&rdquo;</b> thread
294+
{activeThread?.thread?.user?.username
295+
? `, by ${activeThread?.thread?.user?.username}.`
296+
: '.'}
263297
</div>
264298
</>
265-
) : ''}
299+
) : (
300+
''
301+
)}
266302
<ChatLoadingState />
267303
{pair.chatGptMessage.length > 0
268-
? pair.chatGptMessage.map((message) => (
269-
<ChatMessage
270-
key={message.id}
271-
actionRequired={false}
272-
message={message}
273-
sendMessageFromResponse={props.sendMessageFn}
274-
/>
275-
))
304+
? pair.chatGptMessage.map(message => (
305+
<ChatMessage
306+
key={message.id}
307+
actionRequired={false}
308+
message={message}
309+
sendMessageFromResponse={props.sendMessageFn}
310+
/>
311+
))
276312
: ''}
277313
</div>
278314
</SharedAccordion>
279-
);
280-
};
315+
)
316+
}
281317

282318
export function ChatLoadingState() {
283319
const { activeTool, loadingState } = useThread()
@@ -295,7 +331,7 @@ export function ChatLoadingState() {
295331
switch (activeTool?.toolName) {
296332
case 'webSearch':
297333
return (
298-
<div className='flex items-center w-full h-20 gap-4 opacity-65'>
334+
<div className="flex items-center w-full h-20 gap-4 opacity-65">
299335
<GlobeIcon className="relative size-6 animate-bounce top-2" />
300336
<p className="flex flex-col gap-1 leading-none">
301337
<span>
@@ -323,4 +359,4 @@ export function ChatLoadingState() {
323359
</div>
324360
)
325361
}
326-
}
362+
}

0 commit comments

Comments
 (0)