From 82f91c6ced881038d59b9042c8ce543dcd2167ed Mon Sep 17 00:00:00 2001 From: Gabo Esquivel Date: Sat, 6 Apr 2024 23:11:07 -0600 Subject: [PATCH 01/10] feat: thread dialog ui --- .../browse/browse-chat-message-list.tsx | 4 +- .../components/browse/browse-list-item.tsx | 162 +----------------- .../components/shared/chat-dialog.tsx | 135 +++++++++++++++ bun.lockb | Bin 399904 -> 400936 bytes 4 files changed, 141 insertions(+), 160 deletions(-) create mode 100644 apps/masterbots.ai/components/shared/chat-dialog.tsx diff --git a/apps/masterbots.ai/components/browse/browse-chat-message-list.tsx b/apps/masterbots.ai/components/browse/browse-chat-message-list.tsx index efa2afbf..64877504 100644 --- a/apps/masterbots.ai/components/browse/browse-chat-message-list.tsx +++ b/apps/masterbots.ai/components/browse/browse-chat-message-list.tsx @@ -2,11 +2,9 @@ // @see https://github.com/mckaywrigley/chatbot-ui/blob/main/components/Chat/ChatcleanMessage.tsx import type { Chatbot, Message, User } from '@repo/mb-genql' -import Image from 'next/image' -import Link from 'next/link' + import React from 'react' import { cn, createMessagePairs } from '@/lib/utils' -import { IconUser } from '@/components/ui/icons' import { ChatAccordion } from '../shared/chat-accordion' import type { MessagePair } from './browse-chat-messages' import { convertMessage } from './browse-chat-messages' diff --git a/apps/masterbots.ai/components/browse/browse-list-item.tsx b/apps/masterbots.ai/components/browse/browse-list-item.tsx index 1503120e..e1ebc7bc 100644 --- a/apps/masterbots.ai/components/browse/browse-list-item.tsx +++ b/apps/masterbots.ai/components/browse/browse-list-item.tsx @@ -1,14 +1,6 @@ -import Image from 'next/image' -import { Message, Thread } from '@repo/mb-genql' -import Link from 'next/link' -import { useRouter } from 'next/navigation' +import { Thread } from '@repo/mb-genql' import React from 'react' -import { cn, sleep } from '@/lib/utils' -import { getMessages } from '@/services/hasura' -import { ChatAccordion } from '../shared/chat-accordion' -import { ShortMessage } from '../short-message' -import { IconOpenAI, IconUser } from '../ui/icons' -import { BrowseChatMessageList } from './browse-chat-message-list' +import { ChatDialog } from '../shared/chat-dialog' export default function BrowseListItem({ thread, @@ -26,20 +18,15 @@ export default function BrowseListItem({ pageType?: string }) { const threadRef = React.useRef(null) - const router = useRouter() - const [messages, setMessages] = React.useState([]) - // ! Move to custom hook and add it to the context useThread + useProvider @bran18 - const [isAccordionOpen, setIsAccordionOpen] = React.useState(false) React.useEffect(() => { - if (!threadRef.current) return + if (!threadRef.current && !isLast) return const observer = new IntersectionObserver(([entry]) => { - if (hasMore && isLast && entry.isIntersecting && !loading) { + if (hasMore && entry.isIntersecting && !loading) { const timeout = setTimeout(() => { loadMore() clearTimeout(timeout) }, 150) - observer.unobserve(entry.target) } }) @@ -51,148 +38,9 @@ export default function BrowseListItem({ } }, [isLast, hasMore, loading, loadMore]) - const fetchMessages = async () => { - const messages = await getMessages({ threadId: thread.threadId }) - setMessages(_prev => messages) - } - - const handleAccordionToggle = async (isOpen: boolean) => { - if (isOpen) { - setMessages(_prev => []) - await fetchMessages() - } - // When toggling accordion, it should scroll - // Use optional chaining to ensure scrollIntoView is called only if current is not null - await sleep(300) // animation time - threadRef.current.scrollIntoView({ behavior: 'smooth', block: 'start' }) - setIsAccordionOpen(isOpen) - // Should fetch messages only when opening thread. - } - - const goToThread = () => { - router.push( - `/b/${thread.chatbot.name.trim().toLowerCase()}/${thread.threadId}` - ) - router.refresh() - } - return (
- - {/* Thread Title */} -
- {pageType !== 'bot' && thread.chatbot.avatar ? ( - - {thread.chatbot.name - - ) : ( - pageType !== 'bot' && ( - - - - ) - )} -
-
- {thread.messages[0]?.content} -
- {pageType !== 'user' && ( - by - )} - {pageType !== 'user' && thread.user.profilePicture ? ( - - {thread.user.username - - ) : ( - pageType !== 'user' && ( - - - - ) - )} -
-
- - {/* Thread Description */} - -
- {thread.messages[1]?.content && - thread.messages[1]?.role !== 'user' ? ( -
- -
- ) : ( - '' - )} -
- - {/* Thread Content */} - - -
+
) } diff --git a/apps/masterbots.ai/components/shared/chat-dialog.tsx b/apps/masterbots.ai/components/shared/chat-dialog.tsx new file mode 100644 index 00000000..ff7e3008 --- /dev/null +++ b/apps/masterbots.ai/components/shared/chat-dialog.tsx @@ -0,0 +1,135 @@ +'use client' + +import * as React from 'react' +import type { Thread } from '@repo/mb-genql' +import { cn } from '@/lib/utils' +import Image from 'next/image' +import { Dialog, DialogContent, DialogTrigger } from '@/components/ui/dialog' +import Link from 'next/link' +import { IconOpenAI, IconUser } from '../ui/icons' +import { ShortMessage } from '../short-message' +import { BrowseChatMessageList } from '../browse/browse-chat-message-list' +import { useAsync } from 'react-use' +import { getMessages } from '@/services/hasura' + +export function ChatDialog({ thread = null, pageType }: ChatDialogProps) { + const messages = useAsync(async () => + getMessages({ threadId: thread.threadId }) + ) + return ( + + +
div>div>button]:!hidden`)} + > + {/* Thread Title */} +
+ {pageType !== 'bot' && thread.chatbot.avatar ? ( + + {thread.chatbot.name + + ) : ( + pageType !== 'bot' && ( + + + + ) + )} +
+
+ {messages[0]?.content} +
+ {pageType !== 'user' && ( + by + )} + {pageType !== 'user' && thread.user.profilePicture ? ( + + {thread.user.username + + ) : ( + pageType !== 'user' && ( + + + + ) + )} +
+ {messages[1]?.content && messages[1]?.role !== 'user' ? ( +
+ +
+ ) : ( + '' + )} +
+
+
+
+
+ + + +
+ ) +} + +interface ChatDialogProps { + className?: string + handleOpen?: () => void + thread?: Thread | null + pageType: string +} diff --git a/bun.lockb b/bun.lockb index d05fce615e93251511ceed03b34630ba9e3267c7..1fa6a41fe3eb1fa3a430c1ede964b8ca1bda38ff 100755 GIT binary patch delta 55178 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 delta 55306 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 BNml>> From d43596eae8646352a128836093d1662b0d6a95ed Mon Sep 17 00:00:00 2001 From: Gabo Esquivel Date: Sun, 7 Apr 2024 10:13:34 -0600 Subject: [PATCH 02/10] feat: thread dialog ui --- .../app/(browse)/[category]/page.tsx | 4 +- apps/masterbots.ai/app/(browse)/page.tsx | 2 +- apps/masterbots.ai/app/b/[id]/page.tsx | 14 +- apps/masterbots.ai/app/u/[slug]/page.tsx | 9 -- .../bot-details.tsx} | 0 .../browse/browse-category-link.tsx | 2 +- .../browse/browse-category-tabs.tsx | 2 +- .../browse/browse-chat-message-list.tsx | 2 +- .../browse/browse-chat-messages.tsx | 2 +- .../components/browse/browse-list-item.tsx | 46 ------ .../components/browse/browse-list.tsx | 77 ---------- .../browse/browse-specific-thread-list.tsx | 53 ------- .../{ => c}/button-scroll-to-bottom.tsx | 0 apps/masterbots.ai/components/c/chat-list.tsx | 4 +- .../masterbots.ai/components/c/chat-panel.tsx | 10 +- .../components/c/thread-list.tsx | 4 +- .../masterbots.ai/components/empty-screen.tsx | 54 ------- .../components/shared/chat-dialog.tsx | 135 ------------------ .../components/shared/mb-avatar.tsx | 29 ++++ .../components/shared/thread-dialog/index.ts | 1 + .../thread-accordion.tsx} | 0 .../shared/thread-dialog/thread-dialog.tsx | 96 +++++++++++++ .../thread-dialog/thread-excerpt.tsx} | 4 +- .../shared/thread-dialog/thread-list.tsx | 91 ++++++++++++ 24 files changed, 239 insertions(+), 402 deletions(-) rename apps/masterbots.ai/components/{browse/browse-chatbot-details.tsx => b/bot-details.tsx} (100%) delete mode 100644 apps/masterbots.ai/components/browse/browse-list-item.tsx delete mode 100644 apps/masterbots.ai/components/browse/browse-list.tsx delete mode 100644 apps/masterbots.ai/components/browse/browse-specific-thread-list.tsx rename apps/masterbots.ai/components/{ => c}/button-scroll-to-bottom.tsx (100%) delete mode 100644 apps/masterbots.ai/components/empty-screen.tsx delete mode 100644 apps/masterbots.ai/components/shared/chat-dialog.tsx create mode 100644 apps/masterbots.ai/components/shared/mb-avatar.tsx create mode 100644 apps/masterbots.ai/components/shared/thread-dialog/index.ts rename apps/masterbots.ai/components/shared/{chat-accordion.tsx => thread-dialog/thread-accordion.tsx} (100%) create mode 100644 apps/masterbots.ai/components/shared/thread-dialog/thread-dialog.tsx rename apps/masterbots.ai/components/{short-message.tsx => shared/thread-dialog/thread-excerpt.tsx} (93%) create mode 100644 apps/masterbots.ai/components/shared/thread-dialog/thread-list.tsx diff --git a/apps/masterbots.ai/app/(browse)/[category]/page.tsx b/apps/masterbots.ai/app/(browse)/[category]/page.tsx index 05db7811..6e85dee8 100644 --- a/apps/masterbots.ai/app/(browse)/[category]/page.tsx +++ b/apps/masterbots.ai/app/(browse)/[category]/page.tsx @@ -1,4 +1,4 @@ -import BrowseList from '@/components/browse/browse-list' +import BrowseList from '@/components/shared/thread-dialog/thread-list' import { BrowseCategoryTabs } from '@/components/browse/browse-category-tabs' import { BrowseSearchInput } from '@/components/browse/browse-search-input' import { getBrowseThreads, getCategories } from '@/services/hasura' @@ -13,7 +13,7 @@ export default async function BrowseCategoryPage({ const categories = await getCategories() const categoryId = categories.find( c => - c.name.toLowerCase().replace(/\s+/g, '_').replace(/\&/g, 'n') === + c.name.toLowerCase().replace(/\s+/g, '_').replace(/\&/g, '_') === params.category ).categoryId if (!categoryId) throw new Error('Category id not foud') diff --git a/apps/masterbots.ai/app/(browse)/page.tsx b/apps/masterbots.ai/app/(browse)/page.tsx index 719ed596..9fb09e1e 100644 --- a/apps/masterbots.ai/app/(browse)/page.tsx +++ b/apps/masterbots.ai/app/(browse)/page.tsx @@ -1,4 +1,4 @@ -import BrowseList from '@/components/browse/browse-list' +import BrowseList from '@/components/shared/thread-dialog/thread-list' import { BrowseCategoryTabs } from '@/components/browse/browse-category-tabs' import { BrowseSearchInput } from '@/components/browse/browse-search-input' import { getBrowseThreads, getCategories } from '@/services/hasura' diff --git a/apps/masterbots.ai/app/b/[id]/page.tsx b/apps/masterbots.ai/app/b/[id]/page.tsx index f0f8b3e4..6d5d9911 100644 --- a/apps/masterbots.ai/app/b/[id]/page.tsx +++ b/apps/masterbots.ai/app/b/[id]/page.tsx @@ -1,7 +1,6 @@ import { getChatbot, getBrowseThreads } from '@/services/hasura' import { botNames } from '@/lib/bots-names' -import BrowseChatbotDetails from '@/components/browse/browse-chatbot-details' -import BrowseSpecificThreadList from '@/components/browse/browse-specific-thread-list' +import BotDetails from '@/components/b/bot-details' const PAGE_SIZE = 50 @@ -19,7 +18,6 @@ export default async function BotThreadsPage({ }) if (!chatbot) throw new Error(`Chatbot ${botNames.get(params.id)} not found`) - // session will always be defined threads = await getBrowseThreads({ chatbotName: botNames.get(params.id), limit: PAGE_SIZE @@ -27,15 +25,7 @@ export default async function BotThreadsPage({ return (
- {chatbot ? : ''} - +
) } diff --git a/apps/masterbots.ai/app/u/[slug]/page.tsx b/apps/masterbots.ai/app/u/[slug]/page.tsx index 729d66ea..495f20db 100644 --- a/apps/masterbots.ai/app/u/[slug]/page.tsx +++ b/apps/masterbots.ai/app/u/[slug]/page.tsx @@ -1,6 +1,5 @@ import { getBrowseThreads, getUserInfoFromBrowse } from '@/services/hasura' import BrowseUserDetails from '@/components/browse/browse-user-details' -import BrowseSpecificThreadList from '@/components/browse/browse-specific-thread-list' const PAGE_SIZE = 50 @@ -18,14 +17,6 @@ export default async function BotThreadsPage({ return (
-
) } diff --git a/apps/masterbots.ai/components/browse/browse-chatbot-details.tsx b/apps/masterbots.ai/components/b/bot-details.tsx similarity index 100% rename from apps/masterbots.ai/components/browse/browse-chatbot-details.tsx rename to apps/masterbots.ai/components/b/bot-details.tsx diff --git a/apps/masterbots.ai/components/browse/browse-category-link.tsx b/apps/masterbots.ai/components/browse/browse-category-link.tsx index c4b1b4fa..21b3c0f3 100644 --- a/apps/masterbots.ai/components/browse/browse-category-link.tsx +++ b/apps/masterbots.ai/components/browse/browse-category-link.tsx @@ -24,7 +24,7 @@ export function BrowseCategoryLink({ href={ category === 'all' ? '/' - : `/${category.name.toLowerCase().replace(/\s+/g, '_').replace(/\&/g, 'n')}` + : `/${category.name.toLowerCase().replace(/\s+/g, '_').replace(/\&/g, '_')}` } id={id} onClick={onClick} diff --git a/apps/masterbots.ai/components/browse/browse-category-tabs.tsx b/apps/masterbots.ai/components/browse/browse-category-tabs.tsx index 9f212b6f..348c1d4b 100644 --- a/apps/masterbots.ai/components/browse/browse-category-tabs.tsx +++ b/apps/masterbots.ai/components/browse/browse-category-tabs.tsx @@ -36,7 +36,7 @@ export function BrowseCategoryTabs({ setActiveTab( categories.filter( c => - c.name.toLowerCase().replace(/\s+/g, '_').replace(/\&/g, 'n') === + c.name.toLowerCase().replace(/\s+/g, '_').replace(/\&/g, '_') === initialCategory )[0]?.categoryId ) diff --git a/apps/masterbots.ai/components/browse/browse-chat-message-list.tsx b/apps/masterbots.ai/components/browse/browse-chat-message-list.tsx index 64877504..a92f313a 100644 --- a/apps/masterbots.ai/components/browse/browse-chat-message-list.tsx +++ b/apps/masterbots.ai/components/browse/browse-chat-message-list.tsx @@ -5,7 +5,7 @@ import type { Chatbot, Message, User } from '@repo/mb-genql' import React from 'react' import { cn, createMessagePairs } from '@/lib/utils' -import { ChatAccordion } from '../shared/chat-accordion' +import { ChatAccordion } from '../shared/thread-dialog/thread-accordion' import type { MessagePair } from './browse-chat-messages' import { convertMessage } from './browse-chat-messages' import { BrowseChatMessage } from './browse-chat-message' diff --git a/apps/masterbots.ai/components/browse/browse-chat-messages.tsx b/apps/masterbots.ai/components/browse/browse-chat-messages.tsx index c219f4c5..a8c94dc9 100644 --- a/apps/masterbots.ai/components/browse/browse-chat-messages.tsx +++ b/apps/masterbots.ai/components/browse/browse-chat-messages.tsx @@ -5,7 +5,7 @@ import type * as AI from 'ai' import type { Chatbot, Message, User } from '@repo/mb-genql' import React from 'react' import { getMessages } from '@/services/hasura' -import BrowseChatbotDetails from './browse-chatbot-details' +import BrowseChatbotDetails from '../b/bot-details' import { BrowseChatMessageList } from './browse-chat-message-list' export interface MessagePair { diff --git a/apps/masterbots.ai/components/browse/browse-list-item.tsx b/apps/masterbots.ai/components/browse/browse-list-item.tsx deleted file mode 100644 index e1ebc7bc..00000000 --- a/apps/masterbots.ai/components/browse/browse-list-item.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import { Thread } from '@repo/mb-genql' -import React from 'react' -import { ChatDialog } from '../shared/chat-dialog' - -export default function BrowseListItem({ - thread, - loadMore, - loading, - isLast, - hasMore, - pageType = '' -}: { - thread: Thread - loadMore: () => void - loading: boolean - isLast: boolean - hasMore: boolean - pageType?: string -}) { - const threadRef = React.useRef(null) - - React.useEffect(() => { - if (!threadRef.current && !isLast) return - const observer = new IntersectionObserver(([entry]) => { - if (hasMore && entry.isIntersecting && !loading) { - const timeout = setTimeout(() => { - loadMore() - clearTimeout(timeout) - }, 150) - observer.unobserve(entry.target) - } - }) - - observer.observe(threadRef.current) - - return () => { - observer.disconnect() - } - }, [isLast, hasMore, loading, loadMore]) - - return ( -
- -
- ) -} diff --git a/apps/masterbots.ai/components/browse/browse-list.tsx b/apps/masterbots.ai/components/browse/browse-list.tsx deleted file mode 100644 index 7d0f9559..00000000 --- a/apps/masterbots.ai/components/browse/browse-list.tsx +++ /dev/null @@ -1,77 +0,0 @@ -'use client' - -import React from 'react' -import { debounce } from 'lodash' -import type { Thread } from '@repo/mb-genql' -import { useBrowse } from '@/hooks/use-browse' -import { getBrowseThreads } from '@/services/hasura' -import BrowseListItem from './browse-list-item' - -const PAGE_SIZE = 50 - -export default function BrowseList({ initialThreads }: BrowseListProps) { - const { keyword, tab } = useBrowse() - const [threads, setThreads] = React.useState(initialThreads) - const [filteredThreads, setFilteredThreads] = - React.useState(initialThreads) - const [loading, setLoading] = React.useState(false) - const [count, setCount] = React.useState(initialThreads.length) - - const verifyKeyword = () => { - if (!keyword) { - setFilteredThreads(threads) - } else { - debounce(() => { - // TODO: Improve thread messages architecture to implement dynamic search to show only the thread title (first message on thread) - // fetchThreads(keyword, tab) - setFilteredThreads( - threads.filter((thread: Thread) => - thread.messages[0]?.content - .toLowerCase() - .includes(keyword.toLowerCase()) - ) - ) - // ? Average time of human reaction is 230ms - }, 230)() - } - } - - const loadMore = async () => { - console.log('🟡 Loading More Content') - setLoading(true) - - const moreThreads = await getBrowseThreads({ - categoryId: tab, - offset: threads.length, - limit: PAGE_SIZE - }) - - setThreads(prevState => [...prevState, ...moreThreads]) - setCount(moreThreads.length) - setLoading(false) - } - - React.useEffect(() => { - verifyKeyword() - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [keyword, threads]) - - return ( -
- {filteredThreads.map((thread: Thread, key) => ( - - ))} -
- ) -} - -type BrowseListProps = { - initialThreads: Thread[] -} diff --git a/apps/masterbots.ai/components/browse/browse-specific-thread-list.tsx b/apps/masterbots.ai/components/browse/browse-specific-thread-list.tsx deleted file mode 100644 index 4cbf613e..00000000 --- a/apps/masterbots.ai/components/browse/browse-specific-thread-list.tsx +++ /dev/null @@ -1,53 +0,0 @@ -'use client' - -import React from 'react' -import type { Thread } from '@repo/mb-genql' -import { getBrowseThreads } from '@/services/hasura' -import BrowseListItem from './browse-list-item' - -export default function BrowseSpecificThreadList({ - initialThreads, - query, - PAGE_SIZE, - pageType = '' -}: { - query: Record - initialThreads: Thread[] - PAGE_SIZE: number - pageType?: string -}) { - const [threads, setThreads] = React.useState(initialThreads) - const [loading, setLoading] = React.useState(false) - const [count, setCount] = React.useState(initialThreads.length) - - const loadMore = async () => { - console.log('🟡 Loading More Content') - setLoading(true) - - const moreThreads = await getBrowseThreads({ - ...query, - limit: PAGE_SIZE, - offset: threads.length - }) - - setThreads(prevState => [...prevState, ...moreThreads]) - setCount(moreThreads.length) - setLoading(false) - } - - return ( -
- {threads.map((thread: Thread, key) => ( - - ))} -
- ) -} diff --git a/apps/masterbots.ai/components/button-scroll-to-bottom.tsx b/apps/masterbots.ai/components/c/button-scroll-to-bottom.tsx similarity index 100% rename from apps/masterbots.ai/components/button-scroll-to-bottom.tsx rename to apps/masterbots.ai/components/c/button-scroll-to-bottom.tsx diff --git a/apps/masterbots.ai/components/c/chat-list.tsx b/apps/masterbots.ai/components/c/chat-list.tsx index bd09ec73..a3f1317e 100644 --- a/apps/masterbots.ai/components/c/chat-list.tsx +++ b/apps/masterbots.ai/components/c/chat-list.tsx @@ -4,8 +4,8 @@ import React from 'react' import { ChatMessage } from '@/components/c/chat-message' import { cn, createMessagePairs } from '@/lib/utils' import { useThread } from '@/hooks/use-thread' -import { ShortMessage } from '../short-message' -import { ChatAccordion } from '../shared/chat-accordion' +import { ShortMessage } from '../shared/thread-dialog/thread-excerpt' +import { ChatAccordion } from '../shared/thread-dialog/thread-accordion' export interface ChatList { messages: Message[] diff --git a/apps/masterbots.ai/components/c/chat-panel.tsx b/apps/masterbots.ai/components/c/chat-panel.tsx index cbaeb51d..04498e99 100644 --- a/apps/masterbots.ai/components/c/chat-panel.tsx +++ b/apps/masterbots.ai/components/c/chat-panel.tsx @@ -3,7 +3,7 @@ import { type UseChatHelpers } from 'ai/react' import { Chatbot } from '@repo/mb-genql' import { Button } from '@/components/ui/button' import { PromptForm } from '@/components/c/prompt-form' -import { ButtonScrollToBottom } from '@/components/button-scroll-to-bottom' +import { ButtonScrollToBottom } from '@/components/c/button-scroll-to-bottom' import { IconRefresh, IconShare, IconStop } from '@/components/ui/icons' import { FooterText } from '@/components/layout/footer' import { ChatShareDialog } from '@/components/c/chat-share-dialog' @@ -67,7 +67,9 @@ export function ChatPanel({ {isLoading ? ( - ))} - - - - ) -} diff --git a/apps/masterbots.ai/components/shared/chat-dialog.tsx b/apps/masterbots.ai/components/shared/chat-dialog.tsx deleted file mode 100644 index ff7e3008..00000000 --- a/apps/masterbots.ai/components/shared/chat-dialog.tsx +++ /dev/null @@ -1,135 +0,0 @@ -'use client' - -import * as React from 'react' -import type { Thread } from '@repo/mb-genql' -import { cn } from '@/lib/utils' -import Image from 'next/image' -import { Dialog, DialogContent, DialogTrigger } from '@/components/ui/dialog' -import Link from 'next/link' -import { IconOpenAI, IconUser } from '../ui/icons' -import { ShortMessage } from '../short-message' -import { BrowseChatMessageList } from '../browse/browse-chat-message-list' -import { useAsync } from 'react-use' -import { getMessages } from '@/services/hasura' - -export function ChatDialog({ thread = null, pageType }: ChatDialogProps) { - const messages = useAsync(async () => - getMessages({ threadId: thread.threadId }) - ) - return ( - - -
div>div>button]:!hidden`)} - > - {/* Thread Title */} -
- {pageType !== 'bot' && thread.chatbot.avatar ? ( - - {thread.chatbot.name - - ) : ( - pageType !== 'bot' && ( - - - - ) - )} -
-
- {messages[0]?.content} -
- {pageType !== 'user' && ( - by - )} - {pageType !== 'user' && thread.user.profilePicture ? ( - - {thread.user.username - - ) : ( - pageType !== 'user' && ( - - - - ) - )} -
- {messages[1]?.content && messages[1]?.role !== 'user' ? ( -
- -
- ) : ( - '' - )} -
-
-
-
-
- - - -
- ) -} - -interface ChatDialogProps { - className?: string - handleOpen?: () => void - thread?: Thread | null - pageType: string -} diff --git a/apps/masterbots.ai/components/shared/mb-avatar.tsx b/apps/masterbots.ai/components/shared/mb-avatar.tsx new file mode 100644 index 00000000..10612c58 --- /dev/null +++ b/apps/masterbots.ai/components/shared/mb-avatar.tsx @@ -0,0 +1,29 @@ +import { cn } from '@/lib/utils' +import Image from 'next/image' +import Link from 'next/link' + +export function MbAvatar({ href, alt, src }: MbAvatarProp) { + return ( + + {alt} + + ) +} + +interface MbAvatarProp { + href: string + alt: string + src: string +} diff --git a/apps/masterbots.ai/components/shared/thread-dialog/index.ts b/apps/masterbots.ai/components/shared/thread-dialog/index.ts new file mode 100644 index 00000000..97ac0f47 --- /dev/null +++ b/apps/masterbots.ai/components/shared/thread-dialog/index.ts @@ -0,0 +1 @@ +export * from './thread-dialog' diff --git a/apps/masterbots.ai/components/shared/chat-accordion.tsx b/apps/masterbots.ai/components/shared/thread-dialog/thread-accordion.tsx similarity index 100% rename from apps/masterbots.ai/components/shared/chat-accordion.tsx rename to apps/masterbots.ai/components/shared/thread-dialog/thread-accordion.tsx diff --git a/apps/masterbots.ai/components/shared/thread-dialog/thread-dialog.tsx b/apps/masterbots.ai/components/shared/thread-dialog/thread-dialog.tsx new file mode 100644 index 00000000..c6fe7dd4 --- /dev/null +++ b/apps/masterbots.ai/components/shared/thread-dialog/thread-dialog.tsx @@ -0,0 +1,96 @@ +'use client' + +import type { Thread } from '@repo/mb-genql' +import { cn } from '@/lib/utils' +import { Dialog, DialogContent, DialogTrigger } from '@/components/ui/dialog' +import { ShortMessage } from './thread-excerpt' +import { BrowseChatMessageList } from '../../browse/browse-chat-message-list' +import { useAsync } from 'react-use' +import { getMessages } from '@/services/hasura' +import { useEffect } from 'react' +import { MbAvatar } from '../mb-avatar' + +export function ThreadDialog({ thread, excerpt, question }: ThreadDialogProps) { + const messages = useAsync(async () => + getMessages({ threadId: thread.threadId }) + ) + + // update url when dialog opens and closes + useEffect(() => { + const initialUrl = location.href + const threadUrl = `/${thread.chatbot.categories[0].category.name.toLowerCase().replaceAll('', '_').replaceAll('&', '_')}/${thread.threadId}` + history.pushState({}, threadUrl) + return () => { + history.pushState({}, initialUrl) + } + }) + + return ( + + + + + + + ) +} + +function ThreadDialogTrigger({ thread, excerpt, question }: ThreadDialogProps) { + return ( + +
div>div>button]:!hidden`)} + > +
+ + +
+ {question} + + by + + + +
+
+ +
+
+
+
+
+
+ ) +} + +interface ThreadDialogProps { + className?: string + handleOpen?: () => void + thread: Thread + excerpt: string + question: string +} diff --git a/apps/masterbots.ai/components/short-message.tsx b/apps/masterbots.ai/components/shared/thread-dialog/thread-excerpt.tsx similarity index 93% rename from apps/masterbots.ai/components/short-message.tsx rename to apps/masterbots.ai/components/shared/thread-dialog/thread-excerpt.tsx index 0288cee3..547a4c66 100644 --- a/apps/masterbots.ai/components/short-message.tsx +++ b/apps/masterbots.ai/components/shared/thread-dialog/thread-excerpt.tsx @@ -1,7 +1,7 @@ import remarkGfm from 'remark-gfm' import remarkMath from 'remark-math' -import { MemoizedReactMarkdown } from './markdown' -import { CodeBlock } from './ui/codeblock' +import { MemoizedReactMarkdown } from '../../markdown' +import { CodeBlock } from '../../ui/codeblock' export function ShortMessage({ content }: { content: string }) { return ( diff --git a/apps/masterbots.ai/components/shared/thread-dialog/thread-list.tsx b/apps/masterbots.ai/components/shared/thread-dialog/thread-list.tsx new file mode 100644 index 00000000..2f34b275 --- /dev/null +++ b/apps/masterbots.ai/components/shared/thread-dialog/thread-list.tsx @@ -0,0 +1,91 @@ +'use client' + +import React, { useEffect, useRef, useState } from 'react' +import { debounce } from 'lodash' +import type { Thread } from '@repo/mb-genql' +import { useBrowse } from '@/hooks/use-browse' +import { getBrowseThreads } from '@/services/hasura' +import { ThreadDialog } from './thread-dialog' + +export default function BrowseList({ initialThreads }: BrowseListProps) { + const { keyword, tab } = useBrowse() + const [threads, setThreads] = useState(initialThreads) + const [filteredThreads, setFilteredThreads] = + useState(initialThreads) + const [loading, setLoading] = useState(false) + const loadMoreRef = useRef(null) + const [hasMore, setHasMore] = useState(true) + + // load more threads for the category + const loadMore = async () => { + console.log('🟡 Loading More Content') + setLoading(true) + + const moreThreads = await getBrowseThreads({ + categoryId: tab, + offset: filteredThreads.length, + limit: 50 + }) + + if (moreThreads.length === 0) setHasMore(false) + setThreads(prevState => [...prevState, ...moreThreads]) + setLoading(false) + } + + const verifyKeyword = () => { + if (!keyword) { + setFilteredThreads(threads) + } else { + debounce(() => { + setFilteredThreads( + threads.filter((thread: Thread) => + thread.messages[0]?.content + .toLowerCase() + .includes(keyword.toLowerCase()) + ) + ) + }, 230)() + } + } + + useEffect(() => { + verifyKeyword() + }, [keyword, threads, verifyKeyword]) + + // load mare item when it gets to the end + useEffect(() => { + if (!loadMoreRef.current) return + const observer = new IntersectionObserver(([entry]) => { + if (hasMore && entry.isIntersecting && !loading) { + setTimeout(() => loadMore(), 150) + observer.unobserve(entry.target) + } + }) + + observer.observe(loadMoreRef.current) + + return () => observer.disconnect() + }, [hasMore, loading, loadMore]) + + return ( +
+ {filteredThreads.map((thread: Thread, key) => ( + + ))} +
+
+ ) +} + +type BrowseListProps = { + initialThreads: Thread[] +} + +const excerpt = + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed id mauris rhoncus, imperdiet dui a, viverra eros. Nullam eget metus ante. Etiam maximus erat ut libero rutrum, non cursus sapien condimentum. Quisque ultricies suscipit augue eu aliquam. Donec fringilla tristique vestibulum.' +const question = 'why is the sky blue?' From ec6a46324ef875a73675b8518638cb8aebc69e61 Mon Sep 17 00:00:00 2001 From: Gabo Esquivel Date: Sun, 7 Apr 2024 18:46:10 -0600 Subject: [PATCH 03/10] feat: thread dialog ui --- .../(browse)/[category]/[threadId]/page.tsx | 23 +++-- .../app/(browse)/[category]/page.tsx | 6 +- apps/masterbots.ai/app/(browse)/page.tsx | 6 +- apps/masterbots.ai/app/b/[id]/page.tsx | 2 +- .../app/c/[chatbot]/[threadId]/page.tsx | 2 +- apps/masterbots.ai/app/c/[chatbot]/page.tsx | 4 +- apps/masterbots.ai/app/c/layout.tsx | 4 +- apps/masterbots.ai/app/c/page.tsx | 4 +- apps/masterbots.ai/app/globals.css | 23 ++--- apps/masterbots.ai/app/p/page.tsx | 2 +- apps/masterbots.ai/app/u/[slug]/page.tsx | 2 +- .../browse/browse-chat-message-list.tsx | 77 -------------- .../browse/browse-chat-messages.tsx | 58 ----------- .../components/browse/browse-thread.tsx | 31 ------ .../components/external-link.tsx | 30 ------ .../components/layout/header.tsx | 4 +- .../components/layout/providers.tsx | 15 ++- .../{ => layout}/tailwind-indicator.tsx | 0 .../components/{ => layout}/theme-toggle.tsx | 0 .../components/{ => layout}/user-menu.tsx | 0 .../components/modal-coming-soon.tsx | 45 -------- .../components/{ => routes}/b/bot-details.tsx | 2 +- .../browse/browse-category-link.tsx | 0 .../browse/browse-category-tabs.tsx | 0 .../browse/browse-search-input.tsx | 0 .../browse/browse-user-details.tsx | 2 +- .../{ => routes}/browse/shortlink-button.tsx | 2 +- .../c/button-scroll-to-bottom.tsx | 0 .../c/chat-accordion.tsx} | 0 .../{ => routes}/c/chat-chatbot-details.tsx | 2 +- .../{ => routes}/c/chat-chatbot.tsx | 0 .../{ => routes}/c/chat-clickable-text.tsx | 0 .../{ => routes}/c/chat-history.tsx | 2 +- .../{ => routes}/c/chat-layout-section.tsx | 0 .../components/{ => routes}/c/chat-list.tsx | 25 ++--- .../{ => routes}/c/chat-message-actions.tsx | 0 .../{ => routes}/c/chat-message.tsx | 9 +- .../components/{ => routes}/c/chat-panel.tsx | 6 +- .../{ => routes}/c/chat-scroll-anchor.tsx | 0 .../{ => routes}/c/chat-search-input.tsx | 0 .../{ => routes}/c/chat-share-dialog.tsx | 0 .../{ => routes}/c/chat-thread-list-panel.tsx | 0 .../components/{ => routes}/c/chat.tsx | 9 +- .../components/{ => routes}/c/new-chat.tsx | 0 .../components/{ => routes}/c/prompt-form.tsx | 0 .../{ => routes/c/sidebar}/clear-history.tsx | 0 .../{ => routes}/c/sidebar/index.tsx | 0 .../c/sidebar/sidebar-actions.tsx | 0 .../c/sidebar/sidebar-category-general.tsx | 0 .../{ => routes}/c/sidebar/sidebar-footer.tsx | 0 .../{ => routes}/c/sidebar/sidebar-item.tsx | 0 .../{ => routes}/c/sidebar/sidebar-items.tsx | 2 +- .../{ => routes}/c/sidebar/sidebar-link.tsx | 2 +- .../{ => routes}/c/sidebar/sidebar-list.tsx | 5 +- .../{ => routes}/c/sidebar/sidebar-mobile.tsx | 2 +- .../c/sidebar/sidebar-responsive.tsx | 2 +- .../{ => routes}/c/sidebar/sidebar-toggle.tsx | 0 .../c/thread-date-range-picker.tsx | 0 .../components/{ => routes}/c/thread-list.tsx | 4 +- .../{ => routes}/c/thread-panel/index.tsx | 2 +- .../c/thread-panel/user-thread-panel.tsx | 4 +- .../{ => routes}/c/thread-popup.tsx | 5 +- .../{ => routes}/p/early-access-from.tsx | 6 +- .../components/{ => shared}/markdown.tsx | 0 .../components/shared/mb-avatar.tsx | 21 ++-- .../components/shared/thread-accordion.tsx | 95 +++++++++++++++++ .../components/shared/thread-dialog.tsx | 38 +++++++ .../components/shared/thread-dialog/index.ts | 1 - .../shared/thread-dialog/thread-dialog.tsx | 96 ------------------ .../components/shared/thread-heading.tsx | 58 +++++++++++ .../{thread-dialog => }/thread-list.tsx | 17 +--- .../thread-message.tsx} | 11 +- ...d-excerpt.tsx => thread-short-message.tsx} | 4 +- apps/masterbots.ai/hooks/use-thread.tsx | 2 +- apps/masterbots.ai/lib/animation.ts | 34 +++++++ apps/masterbots.ai/lib/threads.ts | 71 +++++++++++++ apps/masterbots.ai/lib/utils.ts | 92 ----------------- apps/masterbots.ai/package.json | 1 + .../services/hasura/hasura.service.ts | 6 ++ bun.lockb | Bin 400936 -> 401040 bytes 80 files changed, 424 insertions(+), 554 deletions(-) delete mode 100644 apps/masterbots.ai/components/browse/browse-chat-message-list.tsx delete mode 100644 apps/masterbots.ai/components/browse/browse-chat-messages.tsx delete mode 100644 apps/masterbots.ai/components/browse/browse-thread.tsx delete mode 100644 apps/masterbots.ai/components/external-link.tsx rename apps/masterbots.ai/components/{ => layout}/tailwind-indicator.tsx (100%) rename apps/masterbots.ai/components/{ => layout}/theme-toggle.tsx (100%) rename apps/masterbots.ai/components/{ => layout}/user-menu.tsx (100%) delete mode 100644 apps/masterbots.ai/components/modal-coming-soon.tsx rename apps/masterbots.ai/components/{ => routes}/b/bot-details.tsx (98%) rename apps/masterbots.ai/components/{ => routes}/browse/browse-category-link.tsx (100%) rename apps/masterbots.ai/components/{ => routes}/browse/browse-category-tabs.tsx (100%) rename apps/masterbots.ai/components/{ => routes}/browse/browse-search-input.tsx (100%) rename apps/masterbots.ai/components/{ => routes}/browse/browse-user-details.tsx (97%) rename apps/masterbots.ai/components/{ => routes}/browse/shortlink-button.tsx (97%) rename apps/masterbots.ai/components/{ => routes}/c/button-scroll-to-bottom.tsx (100%) rename apps/masterbots.ai/components/{shared/thread-dialog/thread-accordion.tsx => routes/c/chat-accordion.tsx} (100%) rename apps/masterbots.ai/components/{ => routes}/c/chat-chatbot-details.tsx (98%) rename apps/masterbots.ai/components/{ => routes}/c/chat-chatbot.tsx (100%) rename apps/masterbots.ai/components/{ => routes}/c/chat-clickable-text.tsx (100%) rename apps/masterbots.ai/components/{ => routes}/c/chat-history.tsx (94%) rename apps/masterbots.ai/components/{ => routes}/c/chat-layout-section.tsx (100%) rename apps/masterbots.ai/components/{ => routes}/c/chat-list.tsx (87%) rename apps/masterbots.ai/components/{ => routes}/c/chat-message-actions.tsx (100%) rename apps/masterbots.ai/components/{ => routes}/c/chat-message.tsx (91%) rename apps/masterbots.ai/components/{ => routes}/c/chat-panel.tsx (94%) rename apps/masterbots.ai/components/{ => routes}/c/chat-scroll-anchor.tsx (100%) rename apps/masterbots.ai/components/{ => routes}/c/chat-search-input.tsx (100%) rename apps/masterbots.ai/components/{ => routes}/c/chat-share-dialog.tsx (100%) rename apps/masterbots.ai/components/{ => routes}/c/chat-thread-list-panel.tsx (100%) rename apps/masterbots.ai/components/{ => routes}/c/chat.tsx (96%) rename apps/masterbots.ai/components/{ => routes}/c/new-chat.tsx (100%) rename apps/masterbots.ai/components/{ => routes}/c/prompt-form.tsx (100%) rename apps/masterbots.ai/components/{ => routes/c/sidebar}/clear-history.tsx (100%) rename apps/masterbots.ai/components/{ => routes}/c/sidebar/index.tsx (100%) rename apps/masterbots.ai/components/{ => routes}/c/sidebar/sidebar-actions.tsx (100%) rename apps/masterbots.ai/components/{ => routes}/c/sidebar/sidebar-category-general.tsx (100%) rename apps/masterbots.ai/components/{ => routes}/c/sidebar/sidebar-footer.tsx (100%) rename apps/masterbots.ai/components/{ => routes}/c/sidebar/sidebar-item.tsx (100%) rename apps/masterbots.ai/components/{ => routes}/c/sidebar/sidebar-items.tsx (90%) rename apps/masterbots.ai/components/{ => routes}/c/sidebar/sidebar-link.tsx (99%) rename apps/masterbots.ai/components/{ => routes}/c/sidebar/sidebar-list.tsx (83%) rename apps/masterbots.ai/components/{ => routes}/c/sidebar/sidebar-mobile.tsx (93%) rename apps/masterbots.ai/components/{ => routes}/c/sidebar/sidebar-responsive.tsx (91%) rename apps/masterbots.ai/components/{ => routes}/c/sidebar/sidebar-toggle.tsx (100%) rename apps/masterbots.ai/components/{ => routes}/c/thread-date-range-picker.tsx (100%) rename apps/masterbots.ai/components/{ => routes}/c/thread-list.tsx (97%) rename apps/masterbots.ai/components/{ => routes}/c/thread-panel/index.tsx (77%) rename apps/masterbots.ai/components/{ => routes}/c/thread-panel/user-thread-panel.tsx (96%) rename apps/masterbots.ai/components/{ => routes}/c/thread-popup.tsx (96%) rename apps/masterbots.ai/components/{ => routes}/p/early-access-from.tsx (96%) rename apps/masterbots.ai/components/{ => shared}/markdown.tsx (100%) create mode 100644 apps/masterbots.ai/components/shared/thread-accordion.tsx create mode 100644 apps/masterbots.ai/components/shared/thread-dialog.tsx delete mode 100644 apps/masterbots.ai/components/shared/thread-dialog/index.ts delete mode 100644 apps/masterbots.ai/components/shared/thread-dialog/thread-dialog.tsx create mode 100644 apps/masterbots.ai/components/shared/thread-heading.tsx rename apps/masterbots.ai/components/shared/{thread-dialog => }/thread-list.tsx (77%) rename apps/masterbots.ai/components/{browse/browse-chat-message.tsx => shared/thread-message.tsx} (82%) rename apps/masterbots.ai/components/shared/{thread-dialog/thread-excerpt.tsx => thread-short-message.tsx} (93%) create mode 100644 apps/masterbots.ai/lib/animation.ts create mode 100644 apps/masterbots.ai/lib/threads.ts diff --git a/apps/masterbots.ai/app/(browse)/[category]/[threadId]/page.tsx b/apps/masterbots.ai/app/(browse)/[category]/[threadId]/page.tsx index 1f203e3d..4a3d0699 100644 --- a/apps/masterbots.ai/app/(browse)/[category]/[threadId]/page.tsx +++ b/apps/masterbots.ai/app/(browse)/[category]/[threadId]/page.tsx @@ -1,19 +1,22 @@ -import { getThread } from '@/services/hasura' -import { BrowseThread } from '@/components/browse/browse-thread' +import { getMessagePairs, getThread } from '@/services/hasura' + import type { ChatPageProps } from '@/app/c/[chatbot]/[threadId]/page' -import Shortlink from '@/components/browse/shortlink-button' +import Shortlink from '@/components/routes/browse/shortlink-button' +import { ThreadAccordion } from '@/components/shared/thread-accordion' export default async function ThreadLandingPage({ params }: ChatPageProps) { const thread = await getThread({ threadId: params.threadId }) - return ( - <> -
- -
+ const initialMessagePairs = await getMessagePairs(thread.threadId) - - + return ( +
+ + +
) } diff --git a/apps/masterbots.ai/app/(browse)/[category]/page.tsx b/apps/masterbots.ai/app/(browse)/[category]/page.tsx index 6e85dee8..2320051e 100644 --- a/apps/masterbots.ai/app/(browse)/[category]/page.tsx +++ b/apps/masterbots.ai/app/(browse)/[category]/page.tsx @@ -1,6 +1,6 @@ -import BrowseList from '@/components/shared/thread-dialog/thread-list' -import { BrowseCategoryTabs } from '@/components/browse/browse-category-tabs' -import { BrowseSearchInput } from '@/components/browse/browse-search-input' +import BrowseList from '@/components/shared/thread-list' +import { BrowseCategoryTabs } from '@/components/routes/browse/browse-category-tabs' +import { BrowseSearchInput } from '@/components/routes/browse/browse-search-input' import { getBrowseThreads, getCategories } from '@/services/hasura' export const revalidate = 3600 // revalidate the data at most every hour diff --git a/apps/masterbots.ai/app/(browse)/page.tsx b/apps/masterbots.ai/app/(browse)/page.tsx index 9fb09e1e..0a502c0e 100644 --- a/apps/masterbots.ai/app/(browse)/page.tsx +++ b/apps/masterbots.ai/app/(browse)/page.tsx @@ -1,6 +1,6 @@ -import BrowseList from '@/components/shared/thread-dialog/thread-list' -import { BrowseCategoryTabs } from '@/components/browse/browse-category-tabs' -import { BrowseSearchInput } from '@/components/browse/browse-search-input' +import BrowseList from '@/components/shared/thread-list' +import { BrowseCategoryTabs } from '@/components/routes/browse/browse-category-tabs' +import { BrowseSearchInput } from '@/components/routes/browse/browse-search-input' import { getBrowseThreads, getCategories } from '@/services/hasura' export const revalidate = 3600 // revalidate the data at most every hour diff --git a/apps/masterbots.ai/app/b/[id]/page.tsx b/apps/masterbots.ai/app/b/[id]/page.tsx index 6d5d9911..4a1efe3c 100644 --- a/apps/masterbots.ai/app/b/[id]/page.tsx +++ b/apps/masterbots.ai/app/b/[id]/page.tsx @@ -1,6 +1,6 @@ import { getChatbot, getBrowseThreads } from '@/services/hasura' import { botNames } from '@/lib/bots-names' -import BotDetails from '@/components/b/bot-details' +import BotDetails from '@/components/routes/b/bot-details' const PAGE_SIZE = 50 diff --git a/apps/masterbots.ai/app/c/[chatbot]/[threadId]/page.tsx b/apps/masterbots.ai/app/c/[chatbot]/[threadId]/page.tsx index 3403606f..c7d48640 100644 --- a/apps/masterbots.ai/app/c/[chatbot]/[threadId]/page.tsx +++ b/apps/masterbots.ai/app/c/[chatbot]/[threadId]/page.tsx @@ -2,7 +2,7 @@ import { redirect } from 'next/navigation' import type { Message } from 'ai/react' import { isTokenExpired } from '@repo/mb-lib' import { cookies } from 'next/headers' -import { Chat } from '@/components/c/chat' +import { Chat } from '@/components/routes/c/chat' import { getThread } from '@/services/hasura' import { getUserProfile } from '@/services/supabase' diff --git a/apps/masterbots.ai/app/c/[chatbot]/page.tsx b/apps/masterbots.ai/app/c/[chatbot]/page.tsx index 970aad53..0141b9e1 100644 --- a/apps/masterbots.ai/app/c/[chatbot]/page.tsx +++ b/apps/masterbots.ai/app/c/[chatbot]/page.tsx @@ -3,8 +3,8 @@ import { isTokenExpired } from '@repo/mb-lib' import { nanoid } from 'nanoid' import { cookies } from 'next/headers' import { redirect } from 'next/navigation' -import { ChatChatbot } from '@/components/c/chat-chatbot' -import ThreadPanel from '@/components/c/thread-panel' +import { ChatChatbot } from '@/components/routes/c/chat-chatbot' +import ThreadPanel from '@/components/routes/c/thread-panel' import { botNames } from '@/lib/bots-names' import { getChatbot, getThreads } from '@/services/hasura' import { getUserProfile } from '@/services/supabase' diff --git a/apps/masterbots.ai/app/c/layout.tsx b/apps/masterbots.ai/app/c/layout.tsx index 86ab7fd8..54821f7b 100644 --- a/apps/masterbots.ai/app/c/layout.tsx +++ b/apps/masterbots.ai/app/c/layout.tsx @@ -1,5 +1,5 @@ -import { ChatLayoutSection } from '@/components/c/chat-layout-section' -import { ResponsiveSidebar } from '@/components/c/sidebar/sidebar-responsive' +import { ChatLayoutSection } from '@/components/routes/c/chat-layout-section' +import { ResponsiveSidebar } from '@/components/routes/c/sidebar/sidebar-responsive' import FooterCT from '@/components/layout/footer-ct' interface ChatLayoutProps { diff --git a/apps/masterbots.ai/app/c/page.tsx b/apps/masterbots.ai/app/c/page.tsx index 1979a52f..d0917350 100644 --- a/apps/masterbots.ai/app/c/page.tsx +++ b/apps/masterbots.ai/app/c/page.tsx @@ -1,8 +1,8 @@ import { isTokenExpired } from '@repo/mb-lib' import { redirect } from 'next/navigation' import { cookies } from 'next/headers' -import ChatThreadListPanel from '@/components/c/chat-thread-list-panel' -import ThreadPanel from '@/components/c/thread-panel' +import ChatThreadListPanel from '@/components/routes/c/chat-thread-list-panel' +import ThreadPanel from '@/components/routes/c/thread-panel' import { getThreads } from '@/services/hasura' import { getUserProfile } from '@/services/supabase' diff --git a/apps/masterbots.ai/app/globals.css b/apps/masterbots.ai/app/globals.css index fe0cea82..741d1bc0 100644 --- a/apps/masterbots.ai/app/globals.css +++ b/apps/masterbots.ai/app/globals.css @@ -100,32 +100,21 @@ } .scrollbar { - overflow: auto; + overflow: auto; } .scrollbar::-webkit-scrollbar { - width: 4px; - height: 4px; + width: 1px; + height: 1px; } .scrollbar::-webkit-scrollbar-track, .scrollbar::-webkit-scrollbar-corner { - background: var(--scrollbar-track) !important; + background: var(--scrollbar-track); } .scrollbar::-webkit-scrollbar-thumb { background: var(--scrollbar-thumb); border-radius: 2px; } -/* .scrollbar::-webkit-scrollbar-thumb:hover { - background: var(--scrollbar-thumb-hover); -} */ - -@media screen and (min-width: 1024px) { - .scrollbar::-webkit-scrollbar { - width: 8px; - height: 8px; - } -} - .scrollbar.small-thumb::-webkit-scrollbar-thumb { border-left: 300px solid #f9f9fa; @@ -151,3 +140,7 @@ overflow: visible; text-overflow: clip; } + +.hide-buttons > button { + display: none; +} diff --git a/apps/masterbots.ai/app/p/page.tsx b/apps/masterbots.ai/app/p/page.tsx index d2325d98..2086576f 100644 --- a/apps/masterbots.ai/app/p/page.tsx +++ b/apps/masterbots.ai/app/p/page.tsx @@ -1,5 +1,5 @@ import { Suspense } from 'react' -import { WorkEarlyAccessForm } from '@/components/p/early-access-from' +import { WorkEarlyAccessForm } from '@/components/routes/p/early-access-from' export default function WorkPage() { return ( diff --git a/apps/masterbots.ai/app/u/[slug]/page.tsx b/apps/masterbots.ai/app/u/[slug]/page.tsx index 495f20db..193ae474 100644 --- a/apps/masterbots.ai/app/u/[slug]/page.tsx +++ b/apps/masterbots.ai/app/u/[slug]/page.tsx @@ -1,5 +1,5 @@ import { getBrowseThreads, getUserInfoFromBrowse } from '@/services/hasura' -import BrowseUserDetails from '@/components/browse/browse-user-details' +import BrowseUserDetails from '@/components/routes/browse/browse-user-details' const PAGE_SIZE = 50 diff --git a/apps/masterbots.ai/components/browse/browse-chat-message-list.tsx b/apps/masterbots.ai/components/browse/browse-chat-message-list.tsx deleted file mode 100644 index a92f313a..00000000 --- a/apps/masterbots.ai/components/browse/browse-chat-message-list.tsx +++ /dev/null @@ -1,77 +0,0 @@ -// Inspired by Chatbot-UI and modified to fit the needs of this project -// @see https://github.com/mckaywrigley/chatbot-ui/blob/main/components/Chat/ChatcleanMessage.tsx - -import type { Chatbot, Message, User } from '@repo/mb-genql' - -import React from 'react' -import { cn, createMessagePairs } from '@/lib/utils' -import { ChatAccordion } from '../shared/thread-dialog/thread-accordion' -import type { MessagePair } from './browse-chat-messages' -import { convertMessage } from './browse-chat-messages' -import { BrowseChatMessage } from './browse-chat-message' - -export function BrowseChatMessageList({ - messages, - user, - chatbot, - isThread = false -}: { - messages: Message[] - user?: User - chatbot?: Chatbot - isThread?: boolean -}) { - const [pairs, setPairs] = React.useState([]) - - React.useEffect(() => { - if (messages.length) { - const prePairs: MessagePair[] = createMessagePairs( - messages - ) as MessagePair[] - setPairs(prePairs) - } else setPairs([]) - }, [messages]) - - return ( -
- {pairs.map((pair: MessagePair, key: number) => ( - - {/* Thread Title */} - {key !== 0 || isThread ? ( -
-
- {pair.userMessage.content} -
-
- ) : null} - - {/* Thread Description */} - <> - - {/* Thread Content */} -
- {pair.chatGptMessage.length > 0 - ? pair.chatGptMessage.map((message, index) => ( - - )) - : ''} -
-
- ))} -
- ) -} diff --git a/apps/masterbots.ai/components/browse/browse-chat-messages.tsx b/apps/masterbots.ai/components/browse/browse-chat-messages.tsx deleted file mode 100644 index a8c94dc9..00000000 --- a/apps/masterbots.ai/components/browse/browse-chat-messages.tsx +++ /dev/null @@ -1,58 +0,0 @@ -// Inspired by Chatbot-UI and modified to fit the needs of this project -// @see https://github.com/mckaywrigley/chatbot-ui/blob/main/components/Chat/ChatcleanMessage.tsx - -import type * as AI from 'ai' -import type { Chatbot, Message, User } from '@repo/mb-genql' -import React from 'react' -import { getMessages } from '@/services/hasura' -import BrowseChatbotDetails from '../b/bot-details' -import { BrowseChatMessageList } from './browse-chat-message-list' - -export interface MessagePair { - userMessage: Message - chatGptMessage: Message[] -} - -export function convertMessage(message: Message) { - return { - id: message.messageId, - content: message.content, - createAt: message.createdAt, - role: message.role - } as AI.Message -} - -export function BrowseChatMessages({ - threadId, - user, - chatbot -}: { - threadId: string - user?: User - chatbot?: Chatbot -}) { - const [messages, setMessages] = React.useState([]) - const fetchMessages = async () => { - if (threadId && !messages.length) { - const messages = await getMessages({ threadId }) - setMessages(messages) - } - } - React.useEffect(() => { - fetchMessages() - }, [threadId]) - - return ( -
- -
- -
-
- ) -} diff --git a/apps/masterbots.ai/components/browse/browse-thread.tsx b/apps/masterbots.ai/components/browse/browse-thread.tsx deleted file mode 100644 index aa010ad7..00000000 --- a/apps/masterbots.ai/components/browse/browse-thread.tsx +++ /dev/null @@ -1,31 +0,0 @@ -'use client' - -import type { Thread } from '@repo/mb-genql' -import { cn } from '@/lib/utils' -import { BrowseChatMessages } from './browse-chat-messages' - -export function BrowseThread({ - thread, - className -}: { - thread: Thread - className?: string -}) { - // we merge past assistant and user messages for ui only - // we remove system prompts from ui - // we extend append function to add our system prompts - - return ( -
- {thread.messages.length ? ( - - ) : ( - '' - )} -
- ) -} diff --git a/apps/masterbots.ai/components/external-link.tsx b/apps/masterbots.ai/components/external-link.tsx deleted file mode 100644 index e5a1baee..00000000 --- a/apps/masterbots.ai/components/external-link.tsx +++ /dev/null @@ -1,30 +0,0 @@ -export function ExternalLink({ - href, - children -}: { - href: string - children: React.ReactNode -}) { - return ( -
- {children} - - - ) -} diff --git a/apps/masterbots.ai/components/layout/header.tsx b/apps/masterbots.ai/components/layout/header.tsx index 513c977a..bd35304e 100644 --- a/apps/masterbots.ai/components/layout/header.tsx +++ b/apps/masterbots.ai/components/layout/header.tsx @@ -4,9 +4,9 @@ import { isTokenExpired } from '@repo/mb-lib' import { cookies } from 'next/headers' import { Button } from '@/components/ui/button' import { IconSeparator } from '@/components/ui/icons' -import { UserMenu } from '@/components/user-menu' +import { UserMenu } from '@/components/layout/user-menu' import { getUserProfile } from '@/services/supabase' -import { SidebarToggle } from '../c/sidebar/sidebar-toggle' +import { SidebarToggle } from '../routes/c/sidebar/sidebar-toggle' // https://nextjs.org/docs/app/building-your-application/data-fetching/fetching-caching-and-revalidating diff --git a/apps/masterbots.ai/components/layout/providers.tsx b/apps/masterbots.ai/components/layout/providers.tsx index 8361f35d..09e35119 100644 --- a/apps/masterbots.ai/components/layout/providers.tsx +++ b/apps/masterbots.ai/components/layout/providers.tsx @@ -6,15 +6,20 @@ import type { ThemeProviderProps } from 'next-themes/dist/types' import { SidebarProvider } from '@/hooks/use-sidebar' import { TooltipProvider } from '@/components/ui/tooltip' import { ThreadProvider } from '@/hooks/use-thread' +import { QueryClient, QueryClientProvider } from '@tanstack/react-query' + +const queryClient = new QueryClient() export function Providers({ children, ...props }: ThemeProviderProps) { return ( - - - {children} - - + + + + {children} + + + ) } diff --git a/apps/masterbots.ai/components/tailwind-indicator.tsx b/apps/masterbots.ai/components/layout/tailwind-indicator.tsx similarity index 100% rename from apps/masterbots.ai/components/tailwind-indicator.tsx rename to apps/masterbots.ai/components/layout/tailwind-indicator.tsx diff --git a/apps/masterbots.ai/components/theme-toggle.tsx b/apps/masterbots.ai/components/layout/theme-toggle.tsx similarity index 100% rename from apps/masterbots.ai/components/theme-toggle.tsx rename to apps/masterbots.ai/components/layout/theme-toggle.tsx diff --git a/apps/masterbots.ai/components/user-menu.tsx b/apps/masterbots.ai/components/layout/user-menu.tsx similarity index 100% rename from apps/masterbots.ai/components/user-menu.tsx rename to apps/masterbots.ai/components/layout/user-menu.tsx diff --git a/apps/masterbots.ai/components/modal-coming-soon.tsx b/apps/masterbots.ai/components/modal-coming-soon.tsx deleted file mode 100644 index 6da04058..00000000 --- a/apps/masterbots.ai/components/modal-coming-soon.tsx +++ /dev/null @@ -1,45 +0,0 @@ -'use client' - -import * as React from 'react' -import { cn } from '@/lib/utils' -import { IconClose } from './ui/icons' - -export interface ModalComingSoonProps extends React.ComponentProps<'div'> { - isOpen: boolean - onClose: () => void -} - -export function ModalComingSoon({ - className, - children, - isOpen, - onClose -}: ModalComingSoonProps) { - return ( -
-
-
-
- -
-
- Coming Soon! -
-
-
- ) -} diff --git a/apps/masterbots.ai/components/b/bot-details.tsx b/apps/masterbots.ai/components/routes/b/bot-details.tsx similarity index 98% rename from apps/masterbots.ai/components/b/bot-details.tsx rename to apps/masterbots.ai/components/routes/b/bot-details.tsx index 1145d029..95267e16 100644 --- a/apps/masterbots.ai/components/b/bot-details.tsx +++ b/apps/masterbots.ai/components/routes/b/bot-details.tsx @@ -1,7 +1,7 @@ import type { Chatbot } from '@repo/mb-genql' import Image from 'next/image' import Link from 'next/link' -import { Separator } from '../ui/separator' +import { Separator } from '../../ui/separator' export default function BrowseChatbotDetails({ chatbot diff --git a/apps/masterbots.ai/components/browse/browse-category-link.tsx b/apps/masterbots.ai/components/routes/browse/browse-category-link.tsx similarity index 100% rename from apps/masterbots.ai/components/browse/browse-category-link.tsx rename to apps/masterbots.ai/components/routes/browse/browse-category-link.tsx diff --git a/apps/masterbots.ai/components/browse/browse-category-tabs.tsx b/apps/masterbots.ai/components/routes/browse/browse-category-tabs.tsx similarity index 100% rename from apps/masterbots.ai/components/browse/browse-category-tabs.tsx rename to apps/masterbots.ai/components/routes/browse/browse-category-tabs.tsx diff --git a/apps/masterbots.ai/components/browse/browse-search-input.tsx b/apps/masterbots.ai/components/routes/browse/browse-search-input.tsx similarity index 100% rename from apps/masterbots.ai/components/browse/browse-search-input.tsx rename to apps/masterbots.ai/components/routes/browse/browse-search-input.tsx diff --git a/apps/masterbots.ai/components/browse/browse-user-details.tsx b/apps/masterbots.ai/components/routes/browse/browse-user-details.tsx similarity index 97% rename from apps/masterbots.ai/components/browse/browse-user-details.tsx rename to apps/masterbots.ai/components/routes/browse/browse-user-details.tsx index bb848475..eccfd76a 100644 --- a/apps/masterbots.ai/components/browse/browse-user-details.tsx +++ b/apps/masterbots.ai/components/routes/browse/browse-user-details.tsx @@ -4,7 +4,7 @@ import type { User } from '@repo/mb-genql' import Image from 'next/image' import { useEffect, useState } from 'react' import { getBrowseThreads } from '@/services/hasura' -import { Separator } from '../ui/separator' +import { Separator } from '../../ui/separator' export default function BrowseChatbotDetails({ user }: { user?: User | null }) { const [threadNum, setThreadNum] = useState(0) diff --git a/apps/masterbots.ai/components/browse/shortlink-button.tsx b/apps/masterbots.ai/components/routes/browse/shortlink-button.tsx similarity index 97% rename from apps/masterbots.ai/components/browse/shortlink-button.tsx rename to apps/masterbots.ai/components/routes/browse/shortlink-button.tsx index 03585e9e..85c5bde8 100644 --- a/apps/masterbots.ai/components/browse/shortlink-button.tsx +++ b/apps/masterbots.ai/components/routes/browse/shortlink-button.tsx @@ -21,7 +21,7 @@ export default function Shortlink() { ) return ( - +
diff --git a/apps/masterbots.ai/components/c/button-scroll-to-bottom.tsx b/apps/masterbots.ai/components/routes/c/button-scroll-to-bottom.tsx similarity index 100% rename from apps/masterbots.ai/components/c/button-scroll-to-bottom.tsx rename to apps/masterbots.ai/components/routes/c/button-scroll-to-bottom.tsx diff --git a/apps/masterbots.ai/components/shared/thread-dialog/thread-accordion.tsx b/apps/masterbots.ai/components/routes/c/chat-accordion.tsx similarity index 100% rename from apps/masterbots.ai/components/shared/thread-dialog/thread-accordion.tsx rename to apps/masterbots.ai/components/routes/c/chat-accordion.tsx diff --git a/apps/masterbots.ai/components/c/chat-chatbot-details.tsx b/apps/masterbots.ai/components/routes/c/chat-chatbot-details.tsx similarity index 98% rename from apps/masterbots.ai/components/c/chat-chatbot-details.tsx rename to apps/masterbots.ai/components/routes/c/chat-chatbot-details.tsx index bb71adca..f1c47d38 100644 --- a/apps/masterbots.ai/components/c/chat-chatbot-details.tsx +++ b/apps/masterbots.ai/components/routes/c/chat-chatbot-details.tsx @@ -6,7 +6,7 @@ import { useSidebar } from '@/hooks/use-sidebar' import { getCategory, getThreads } from '@/services/hasura' import { useThread } from '@/hooks/use-thread' import { useGlobalStore } from '@/hooks/use-global-store' -import { Separator } from '../ui/separator' +import { Separator } from '../../ui/separator' export default function ChatChatbotDetails() { const { user, hasuraJwt } = useGlobalStore() diff --git a/apps/masterbots.ai/components/c/chat-chatbot.tsx b/apps/masterbots.ai/components/routes/c/chat-chatbot.tsx similarity index 100% rename from apps/masterbots.ai/components/c/chat-chatbot.tsx rename to apps/masterbots.ai/components/routes/c/chat-chatbot.tsx diff --git a/apps/masterbots.ai/components/c/chat-clickable-text.tsx b/apps/masterbots.ai/components/routes/c/chat-clickable-text.tsx similarity index 100% rename from apps/masterbots.ai/components/c/chat-clickable-text.tsx rename to apps/masterbots.ai/components/routes/c/chat-clickable-text.tsx diff --git a/apps/masterbots.ai/components/c/chat-history.tsx b/apps/masterbots.ai/components/routes/c/chat-history.tsx similarity index 94% rename from apps/masterbots.ai/components/c/chat-history.tsx rename to apps/masterbots.ai/components/routes/c/chat-history.tsx index 5c2a22c7..225dfc34 100644 --- a/apps/masterbots.ai/components/c/chat-history.tsx +++ b/apps/masterbots.ai/components/routes/c/chat-history.tsx @@ -1,7 +1,7 @@ import * as React from 'react' import Link from 'next/link' import { cn } from '@/lib/utils' -import { SidebarList } from '@/components/c/sidebar/sidebar-list' +import { SidebarList } from '@/components/routes/c/sidebar/sidebar-list' import { buttonVariants } from '@/components/ui/button' import { IconPlus } from '@/components/ui/icons' diff --git a/apps/masterbots.ai/components/c/chat-layout-section.tsx b/apps/masterbots.ai/components/routes/c/chat-layout-section.tsx similarity index 100% rename from apps/masterbots.ai/components/c/chat-layout-section.tsx rename to apps/masterbots.ai/components/routes/c/chat-layout-section.tsx diff --git a/apps/masterbots.ai/components/c/chat-list.tsx b/apps/masterbots.ai/components/routes/c/chat-list.tsx similarity index 87% rename from apps/masterbots.ai/components/c/chat-list.tsx rename to apps/masterbots.ai/components/routes/c/chat-list.tsx index a3f1317e..ff94d467 100644 --- a/apps/masterbots.ai/components/c/chat-list.tsx +++ b/apps/masterbots.ai/components/routes/c/chat-list.tsx @@ -1,11 +1,12 @@ import { type Message } from 'ai' import type { Chatbot } from '@repo/mb-genql' import React from 'react' -import { ChatMessage } from '@/components/c/chat-message' -import { cn, createMessagePairs } from '@/lib/utils' +import { ChatMessage } from '@/components/routes/c/chat-message' +import { cn } from '@/lib/utils' import { useThread } from '@/hooks/use-thread' -import { ShortMessage } from '../shared/thread-dialog/thread-excerpt' -import { ChatAccordion } from '../shared/thread-dialog/thread-accordion' +import { ShortMessage } from '../../shared/thread-short-message' +import { ChatAccordion } from './chat-accordion' +import { createMessagePairs } from '@/lib/threads' export interface ChatList { messages: Message[] @@ -36,14 +37,14 @@ export function ChatList({ const [pairs, setPairs] = React.useState([]) const { isNewResponse } = useThread() - React.useEffect(() => { - if (messages.length) { - const prePairs: MessagePair[] = createMessagePairs( - messages - ) as MessagePair[] - setPairs(prePairs) - } else setPairs([]) - }, [messages]) + // React.useEffect(() => { + // if (messages.length) { + // const prePairs: MessagePair[] = createMessagePairs( + // messages as Message[] + // ) as MessagePair[] + // setPairs(prePairs) + // } else setPairs([]) + // }, [messages]) if (!messages.length) return null return ( diff --git a/apps/masterbots.ai/components/c/chat-message-actions.tsx b/apps/masterbots.ai/components/routes/c/chat-message-actions.tsx similarity index 100% rename from apps/masterbots.ai/components/c/chat-message-actions.tsx rename to apps/masterbots.ai/components/routes/c/chat-message-actions.tsx diff --git a/apps/masterbots.ai/components/c/chat-message.tsx b/apps/masterbots.ai/components/routes/c/chat-message.tsx similarity index 91% rename from apps/masterbots.ai/components/c/chat-message.tsx rename to apps/masterbots.ai/components/routes/c/chat-message.tsx index 05700d7e..c3e20772 100644 --- a/apps/masterbots.ai/components/c/chat-message.tsx +++ b/apps/masterbots.ai/components/routes/c/chat-message.tsx @@ -5,11 +5,12 @@ import type { Message } from 'ai' import type { Chatbot } from '@repo/mb-genql' import remarkGfm from 'remark-gfm' import remarkMath from 'remark-math' -import { ClickableText } from '@/components/c/chat-clickable-text' -import { ChatMessageActions } from '@/components/c/chat-message-actions' -import { MemoizedReactMarkdown } from '@/components/markdown' +import { ClickableText } from '@/components/routes/c/chat-clickable-text' +import { ChatMessageActions } from '@/components/routes/c/chat-message-actions' +import { MemoizedReactMarkdown } from '@/components/shared/markdown' import { CodeBlock } from '@/components/ui/codeblock' -import { cleanPrompt, cn } from '@/lib/utils' +import { cn } from '@/lib/utils' +import { cleanPrompt } from '@/lib/threads' export interface ChatMessageProps { message: Message diff --git a/apps/masterbots.ai/components/c/chat-panel.tsx b/apps/masterbots.ai/components/routes/c/chat-panel.tsx similarity index 94% rename from apps/masterbots.ai/components/c/chat-panel.tsx rename to apps/masterbots.ai/components/routes/c/chat-panel.tsx index 04498e99..1861b4b3 100644 --- a/apps/masterbots.ai/components/c/chat-panel.tsx +++ b/apps/masterbots.ai/components/routes/c/chat-panel.tsx @@ -2,11 +2,11 @@ import * as React from 'react' import { type UseChatHelpers } from 'ai/react' import { Chatbot } from '@repo/mb-genql' import { Button } from '@/components/ui/button' -import { PromptForm } from '@/components/c/prompt-form' -import { ButtonScrollToBottom } from '@/components/c/button-scroll-to-bottom' +import { PromptForm } from '@/components/routes/c/prompt-form' +import { ButtonScrollToBottom } from '@/components/routes/c/button-scroll-to-bottom' import { IconRefresh, IconShare, IconStop } from '@/components/ui/icons' import { FooterText } from '@/components/layout/footer' -import { ChatShareDialog } from '@/components/c/chat-share-dialog' +import { ChatShareDialog } from '@/components/routes/c/chat-share-dialog' import { cn } from '@/lib/utils' import { useThread } from '@/hooks/use-thread' diff --git a/apps/masterbots.ai/components/c/chat-scroll-anchor.tsx b/apps/masterbots.ai/components/routes/c/chat-scroll-anchor.tsx similarity index 100% rename from apps/masterbots.ai/components/c/chat-scroll-anchor.tsx rename to apps/masterbots.ai/components/routes/c/chat-scroll-anchor.tsx diff --git a/apps/masterbots.ai/components/c/chat-search-input.tsx b/apps/masterbots.ai/components/routes/c/chat-search-input.tsx similarity index 100% rename from apps/masterbots.ai/components/c/chat-search-input.tsx rename to apps/masterbots.ai/components/routes/c/chat-search-input.tsx diff --git a/apps/masterbots.ai/components/c/chat-share-dialog.tsx b/apps/masterbots.ai/components/routes/c/chat-share-dialog.tsx similarity index 100% rename from apps/masterbots.ai/components/c/chat-share-dialog.tsx rename to apps/masterbots.ai/components/routes/c/chat-share-dialog.tsx diff --git a/apps/masterbots.ai/components/c/chat-thread-list-panel.tsx b/apps/masterbots.ai/components/routes/c/chat-thread-list-panel.tsx similarity index 100% rename from apps/masterbots.ai/components/c/chat-thread-list-panel.tsx rename to apps/masterbots.ai/components/routes/c/chat-thread-list-panel.tsx diff --git a/apps/masterbots.ai/components/c/chat.tsx b/apps/masterbots.ai/components/routes/c/chat.tsx similarity index 96% rename from apps/masterbots.ai/components/c/chat.tsx rename to apps/masterbots.ai/components/routes/c/chat.tsx index e8ccc0ad..b3d6b801 100644 --- a/apps/masterbots.ai/components/c/chat.tsx +++ b/apps/masterbots.ai/components/routes/c/chat.tsx @@ -9,16 +9,17 @@ import type { Chatbot } from '@repo/mb-genql' import { useParams } from 'next/navigation' import React, { useEffect } from 'react' import { toast } from 'react-hot-toast' -import { ChatList } from '@/components/c/chat-list' -import { ChatPanel } from '@/components/c/chat-panel' -import { ChatScrollAnchor } from '@/components/c/chat-scroll-anchor' -import { cn, extractBetweenMarkers, scrollToBottomOfElement } from '@/lib/utils' +import { ChatList } from '@/components/routes/c/chat-list' +import { ChatPanel } from '@/components/routes/c/chat-panel' +import { ChatScrollAnchor } from '@/components/routes/c/chat-scroll-anchor' +import { cn, extractBetweenMarkers } from '@/lib/utils' import { useAtBottom } from '@/hooks/use-at-bottom' import { createThread, getThread, saveNewMessage } from '@/services/hasura' import { useThread } from '@/hooks/use-thread' import { botNames } from '@/lib/bots-names' import { useSidebar } from '@/hooks/use-sidebar' import { useGlobalStore } from '@/hooks/use-global-store' +import { scrollToBottomOfElement } from '@/lib/animation' export function Chat({ initialMessages, diff --git a/apps/masterbots.ai/components/c/new-chat.tsx b/apps/masterbots.ai/components/routes/c/new-chat.tsx similarity index 100% rename from apps/masterbots.ai/components/c/new-chat.tsx rename to apps/masterbots.ai/components/routes/c/new-chat.tsx diff --git a/apps/masterbots.ai/components/c/prompt-form.tsx b/apps/masterbots.ai/components/routes/c/prompt-form.tsx similarity index 100% rename from apps/masterbots.ai/components/c/prompt-form.tsx rename to apps/masterbots.ai/components/routes/c/prompt-form.tsx diff --git a/apps/masterbots.ai/components/clear-history.tsx b/apps/masterbots.ai/components/routes/c/sidebar/clear-history.tsx similarity index 100% rename from apps/masterbots.ai/components/clear-history.tsx rename to apps/masterbots.ai/components/routes/c/sidebar/clear-history.tsx diff --git a/apps/masterbots.ai/components/c/sidebar/index.tsx b/apps/masterbots.ai/components/routes/c/sidebar/index.tsx similarity index 100% rename from apps/masterbots.ai/components/c/sidebar/index.tsx rename to apps/masterbots.ai/components/routes/c/sidebar/index.tsx diff --git a/apps/masterbots.ai/components/c/sidebar/sidebar-actions.tsx b/apps/masterbots.ai/components/routes/c/sidebar/sidebar-actions.tsx similarity index 100% rename from apps/masterbots.ai/components/c/sidebar/sidebar-actions.tsx rename to apps/masterbots.ai/components/routes/c/sidebar/sidebar-actions.tsx diff --git a/apps/masterbots.ai/components/c/sidebar/sidebar-category-general.tsx b/apps/masterbots.ai/components/routes/c/sidebar/sidebar-category-general.tsx similarity index 100% rename from apps/masterbots.ai/components/c/sidebar/sidebar-category-general.tsx rename to apps/masterbots.ai/components/routes/c/sidebar/sidebar-category-general.tsx diff --git a/apps/masterbots.ai/components/c/sidebar/sidebar-footer.tsx b/apps/masterbots.ai/components/routes/c/sidebar/sidebar-footer.tsx similarity index 100% rename from apps/masterbots.ai/components/c/sidebar/sidebar-footer.tsx rename to apps/masterbots.ai/components/routes/c/sidebar/sidebar-footer.tsx diff --git a/apps/masterbots.ai/components/c/sidebar/sidebar-item.tsx b/apps/masterbots.ai/components/routes/c/sidebar/sidebar-item.tsx similarity index 100% rename from apps/masterbots.ai/components/c/sidebar/sidebar-item.tsx rename to apps/masterbots.ai/components/routes/c/sidebar/sidebar-item.tsx diff --git a/apps/masterbots.ai/components/c/sidebar/sidebar-items.tsx b/apps/masterbots.ai/components/routes/c/sidebar/sidebar-items.tsx similarity index 90% rename from apps/masterbots.ai/components/c/sidebar/sidebar-items.tsx rename to apps/masterbots.ai/components/routes/c/sidebar/sidebar-items.tsx index c4491bd8..714b6c82 100644 --- a/apps/masterbots.ai/components/c/sidebar/sidebar-items.tsx +++ b/apps/masterbots.ai/components/routes/c/sidebar/sidebar-items.tsx @@ -2,7 +2,7 @@ import { AnimatePresence, motion } from 'framer-motion' import type { Chat } from '@/lib/types' -import { SidebarItem } from '@/components/c/sidebar/sidebar-item' +import { SidebarItem } from '@/components/routes/c/sidebar/sidebar-item' interface SidebarItemsProps { chats?: Chat[] diff --git a/apps/masterbots.ai/components/c/sidebar/sidebar-link.tsx b/apps/masterbots.ai/components/routes/c/sidebar/sidebar-link.tsx similarity index 99% rename from apps/masterbots.ai/components/c/sidebar/sidebar-link.tsx rename to apps/masterbots.ai/components/routes/c/sidebar/sidebar-link.tsx index c6407316..46d2a4af 100644 --- a/apps/masterbots.ai/components/c/sidebar/sidebar-link.tsx +++ b/apps/masterbots.ai/components/routes/c/sidebar/sidebar-link.tsx @@ -8,7 +8,7 @@ import React from 'react' import { getChatbots } from '@/services/hasura' import { cn } from '@/lib/utils' import { useSidebar } from '@/hooks/use-sidebar' -import { IconCaretRight } from '../../ui/icons' +import { IconCaretRight } from '../../../ui/icons' const PAGE_SIZE = 20 diff --git a/apps/masterbots.ai/components/c/sidebar/sidebar-list.tsx b/apps/masterbots.ai/components/routes/c/sidebar/sidebar-list.tsx similarity index 83% rename from apps/masterbots.ai/components/c/sidebar/sidebar-list.tsx rename to apps/masterbots.ai/components/routes/c/sidebar/sidebar-list.tsx index b1881ebd..f8c45c66 100644 --- a/apps/masterbots.ai/components/c/sidebar/sidebar-list.tsx +++ b/apps/masterbots.ai/components/routes/c/sidebar/sidebar-list.tsx @@ -1,7 +1,6 @@ import { cache } from 'react' -import { ClearHistory } from '@/components/clear-history' -import { SidebarItems } from '@/components/c/sidebar/sidebar-items' -import { ThemeToggle } from '@/components/theme-toggle' +import { SidebarItems } from '@/components/routes/c/sidebar/sidebar-items' +import { ThemeToggle } from '@/components/layout/theme-toggle' interface SidebarListProps { userId?: string diff --git a/apps/masterbots.ai/components/c/sidebar/sidebar-mobile.tsx b/apps/masterbots.ai/components/routes/c/sidebar/sidebar-mobile.tsx similarity index 93% rename from apps/masterbots.ai/components/c/sidebar/sidebar-mobile.tsx rename to apps/masterbots.ai/components/routes/c/sidebar/sidebar-mobile.tsx index ebff107d..eacfa285 100644 --- a/apps/masterbots.ai/components/c/sidebar/sidebar-mobile.tsx +++ b/apps/masterbots.ai/components/routes/c/sidebar/sidebar-mobile.tsx @@ -1,7 +1,7 @@ 'use client' import { Sheet, SheetContent, SheetTrigger } from '@/components/ui/sheet' -import { Sidebar } from '@/components/c/sidebar' +import { Sidebar } from '@/components/routes/c/sidebar' import { Button } from '@/components/ui/button' import { IconSidebar } from '@/components/ui/icons' diff --git a/apps/masterbots.ai/components/c/sidebar/sidebar-responsive.tsx b/apps/masterbots.ai/components/routes/c/sidebar/sidebar-responsive.tsx similarity index 91% rename from apps/masterbots.ai/components/c/sidebar/sidebar-responsive.tsx rename to apps/masterbots.ai/components/routes/c/sidebar/sidebar-responsive.tsx index 0252dfcd..7f0ae2b9 100644 --- a/apps/masterbots.ai/components/c/sidebar/sidebar-responsive.tsx +++ b/apps/masterbots.ai/components/routes/c/sidebar/sidebar-responsive.tsx @@ -1,4 +1,4 @@ -import { Sidebar } from '@/components/c/sidebar' +import { Sidebar } from '@/components/routes/c/sidebar' // import { ChatHistory } from '@/components/chat-history' import { SidebarGeneralCategory } from './sidebar-category-general' diff --git a/apps/masterbots.ai/components/c/sidebar/sidebar-toggle.tsx b/apps/masterbots.ai/components/routes/c/sidebar/sidebar-toggle.tsx similarity index 100% rename from apps/masterbots.ai/components/c/sidebar/sidebar-toggle.tsx rename to apps/masterbots.ai/components/routes/c/sidebar/sidebar-toggle.tsx diff --git a/apps/masterbots.ai/components/c/thread-date-range-picker.tsx b/apps/masterbots.ai/components/routes/c/thread-date-range-picker.tsx similarity index 100% rename from apps/masterbots.ai/components/c/thread-date-range-picker.tsx rename to apps/masterbots.ai/components/routes/c/thread-date-range-picker.tsx diff --git a/apps/masterbots.ai/components/c/thread-list.tsx b/apps/masterbots.ai/components/routes/c/thread-list.tsx similarity index 97% rename from apps/masterbots.ai/components/c/thread-list.tsx rename to apps/masterbots.ai/components/routes/c/thread-list.tsx index 4c39d6b8..b8ac8382 100644 --- a/apps/masterbots.ai/components/c/thread-list.tsx +++ b/apps/masterbots.ai/components/routes/c/thread-list.tsx @@ -6,8 +6,8 @@ import React from 'react' import { useThread } from '@/hooks/use-thread' import { useSidebar } from '@/hooks/use-sidebar' import { cn, sleep } from '@/lib/utils' -import { ShortMessage } from '../shared/thread-dialog/thread-excerpt' -import { ChatAccordion } from '../shared/thread-dialog/thread-accordion' +import { ShortMessage } from '../../shared/thread-short-message' +import { ChatAccordion } from './chat-accordion' import { ChatList } from './chat-list' export default function ThreadList({ diff --git a/apps/masterbots.ai/components/c/thread-panel/index.tsx b/apps/masterbots.ai/components/routes/c/thread-panel/index.tsx similarity index 77% rename from apps/masterbots.ai/components/c/thread-panel/index.tsx rename to apps/masterbots.ai/components/routes/c/thread-panel/index.tsx index db5f9b93..eacc3ff3 100644 --- a/apps/masterbots.ai/components/c/thread-panel/index.tsx +++ b/apps/masterbots.ai/components/routes/c/thread-panel/index.tsx @@ -1,5 +1,5 @@ import type { Thread } from '@repo/mb-genql' -import UserThreadPanel from '@/components/c/thread-panel/user-thread-panel' +import UserThreadPanel from '@/components/routes/c/thread-panel/user-thread-panel' export default async function ThreadPanel({ chatbot, diff --git a/apps/masterbots.ai/components/c/thread-panel/user-thread-panel.tsx b/apps/masterbots.ai/components/routes/c/thread-panel/user-thread-panel.tsx similarity index 96% rename from apps/masterbots.ai/components/c/thread-panel/user-thread-panel.tsx rename to apps/masterbots.ai/components/routes/c/thread-panel/user-thread-panel.tsx index f3eee1d7..e4ddc093 100644 --- a/apps/masterbots.ai/components/c/thread-panel/user-thread-panel.tsx +++ b/apps/masterbots.ai/components/routes/c/thread-panel/user-thread-panel.tsx @@ -2,8 +2,8 @@ import type { Thread } from '@repo/mb-genql' import React, { useEffect, useRef, useState } from 'react' -import { ChatSearchInput } from '@/components/c/chat-search-input' -import ThreadList from '@/components/c/thread-list' +import { ChatSearchInput } from '@/components/routes/c/chat-search-input' +import ThreadList from '@/components/routes/c/thread-list' import { useSidebar } from '@/hooks/use-sidebar' import { useThread } from '@/hooks/use-thread' import { getThreads } from '@/services/hasura' diff --git a/apps/masterbots.ai/components/c/thread-popup.tsx b/apps/masterbots.ai/components/routes/c/thread-popup.tsx similarity index 96% rename from apps/masterbots.ai/components/c/thread-popup.tsx rename to apps/masterbots.ai/components/routes/c/thread-popup.tsx index c090ae60..55bf68fd 100644 --- a/apps/masterbots.ai/components/c/thread-popup.tsx +++ b/apps/masterbots.ai/components/routes/c/thread-popup.tsx @@ -3,11 +3,12 @@ import { useEffect, useRef } from 'react' import { useScroll } from 'framer-motion' import { useThread } from '@/hooks/use-thread' -import { cn, scrollToBottomOfElement } from '@/lib/utils' +import { cn } from '@/lib/utils' import { useAtBottom } from '@/hooks/use-at-bottom' -import { IconClose } from '../ui/icons' +import { IconClose } from '../../ui/icons' import { Chat } from './chat' import { ChatList } from './chat-list' +import { scrollToBottomOfElement } from '@/lib/animation' export function ThreadPopup({ className }: { className?: string }) { const { diff --git a/apps/masterbots.ai/components/p/early-access-from.tsx b/apps/masterbots.ai/components/routes/p/early-access-from.tsx similarity index 96% rename from apps/masterbots.ai/components/p/early-access-from.tsx rename to apps/masterbots.ai/components/routes/p/early-access-from.tsx index cb71bd58..1ab55f8d 100644 --- a/apps/masterbots.ai/components/p/early-access-from.tsx +++ b/apps/masterbots.ai/components/routes/p/early-access-from.tsx @@ -6,7 +6,7 @@ import { zodResolver } from '@hookform/resolvers/zod' import { z } from 'zod' import { Button } from '@/components/ui/button' import { Checkbox } from '@/components/ui/checkbox' -import { Input } from '../ui/input' +import { Input } from '../../ui/input' const schema = z.object({ otherText: z.string().min(1).optional(), @@ -87,7 +87,9 @@ export async function WorkEarlyAccessForm() { )}
{formState.errors.otherText ?

Please specify other interest

: null} - {formState.isSubmitted && formState.errors && !formState.isValid ?

Please select at least one option

: null} + {formState.isSubmitted && formState.errors && !formState.isValid ? ( +

Please select at least one option

+ ) : null}
diff --git a/apps/masterbots.ai/components/markdown.tsx b/apps/masterbots.ai/components/shared/markdown.tsx similarity index 100% rename from apps/masterbots.ai/components/markdown.tsx rename to apps/masterbots.ai/components/shared/markdown.tsx diff --git a/apps/masterbots.ai/components/shared/mb-avatar.tsx b/apps/masterbots.ai/components/shared/mb-avatar.tsx index 10612c58..0072fc95 100644 --- a/apps/masterbots.ai/components/shared/mb-avatar.tsx +++ b/apps/masterbots.ai/components/shared/mb-avatar.tsx @@ -1,6 +1,7 @@ import { cn } from '@/lib/utils' import Image from 'next/image' import Link from 'next/link' +import { IconUser } from '../ui/icons' export function MbAvatar({ href, alt, src }: MbAvatarProp) { return ( @@ -11,13 +12,17 @@ export function MbAvatar({ href, alt, src }: MbAvatarProp) { href={href} title={alt} > - {alt} + {src ? ( + {alt} + ) : ( + + )} ) } @@ -25,5 +30,5 @@ export function MbAvatar({ href, alt, src }: MbAvatarProp) { interface MbAvatarProp { href: string alt: string - src: string + src?: string } diff --git a/apps/masterbots.ai/components/shared/thread-accordion.tsx b/apps/masterbots.ai/components/shared/thread-accordion.tsx new file mode 100644 index 00000000..9f8b16d2 --- /dev/null +++ b/apps/masterbots.ai/components/shared/thread-accordion.tsx @@ -0,0 +1,95 @@ +'use client' + +import { useEffect } from 'react' + +import { getMessagePairs } from '@/services/hasura' + +import { useQuery } from '@tanstack/react-query' +import { + Accordion, + AccordionContent, + AccordionItem, + AccordionTrigger +} from '@/components/ui/accordion' + +import { Thread } from '@repo/mb-genql' +import { ThreadHeading } from './thread-heading' +import { MessagePair, convertMessage } from '@/lib/threads' +import { BrowseChatMessage } from './thread-message' + +export function ThreadAccordion({ + thread, + initialMessagePairs, + clientFetch = false +}: ThreadAccordionProps) { + // initalMessages is coming from server ssr on load. the rest of messages on demand on mount + const { + data: pairs, + isLoading, + error + } = useQuery({ + queryKey: [`messages-${thread.threadId}`], + queryFn: () => getMessagePairs(thread.threadId), + initialData: initialMessagePairs, + refetchOnMount: true, + enabled: clientFetch + }) + + // update url when dialog opens and closes + useEffect(() => { + const initialUrl = location.href + const threadUrl = `/${thread.chatbot.categories[0].category.name.toLowerCase().replaceAll(' ', '_').replaceAll('&', '_')}/${thread.threadId}` + console.log(`Updating URL to ${threadUrl}, initialUrl was ${initialUrl}`) + + window.history.pushState({}, '', threadUrl) + return () => { + window.history.pushState({}, '', initialUrl) + } + }) + + if (error) return
There was an error loading thread messages
+ + // if no initial message and still loading show loading message + if (!pairs?.length && isLoading) return
Loading thread messages ...
+ + console.log(pairs.map((_p, key) => `pair-${key}`)) + return ( + + {pairs.map((p, key) => { + return ( + + + {key ? ( + p.userMessage.content + ) : ( + + )} + + + {p.chatGptMessage.map((message, index) => ( + + ))} + + + ) + })} + + ) +} + +interface ThreadAccordionProps { + thread: Thread + initialMessagePairs?: MessagePair[] + clientFetch?: boolean +} diff --git a/apps/masterbots.ai/components/shared/thread-dialog.tsx b/apps/masterbots.ai/components/shared/thread-dialog.tsx new file mode 100644 index 00000000..f2a414e6 --- /dev/null +++ b/apps/masterbots.ai/components/shared/thread-dialog.tsx @@ -0,0 +1,38 @@ +'use client' + +import type { Thread } from '@repo/mb-genql' +import { Dialog, DialogContent, DialogTrigger } from '@/components/ui/dialog' +import { ThreadAccordion } from './thread-accordion' +import { ThreadHeading } from './thread-heading' +import { cn } from '@/lib/utils' + +export function ThreadDialog({ thread }: ThreadDialogProps) { + const firstQuestion = + thread.messages.find(m => m.role === 'user')?.content || 'not found' + const firstResponse = + thread.messages.find(m => m.role === 'assistant')?.content || 'not found' + + return ( + + + + + + + + + + ) +} + +interface ThreadDialogProps { + thread: Thread +} diff --git a/apps/masterbots.ai/components/shared/thread-dialog/index.ts b/apps/masterbots.ai/components/shared/thread-dialog/index.ts deleted file mode 100644 index 97ac0f47..00000000 --- a/apps/masterbots.ai/components/shared/thread-dialog/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './thread-dialog' diff --git a/apps/masterbots.ai/components/shared/thread-dialog/thread-dialog.tsx b/apps/masterbots.ai/components/shared/thread-dialog/thread-dialog.tsx deleted file mode 100644 index c6fe7dd4..00000000 --- a/apps/masterbots.ai/components/shared/thread-dialog/thread-dialog.tsx +++ /dev/null @@ -1,96 +0,0 @@ -'use client' - -import type { Thread } from '@repo/mb-genql' -import { cn } from '@/lib/utils' -import { Dialog, DialogContent, DialogTrigger } from '@/components/ui/dialog' -import { ShortMessage } from './thread-excerpt' -import { BrowseChatMessageList } from '../../browse/browse-chat-message-list' -import { useAsync } from 'react-use' -import { getMessages } from '@/services/hasura' -import { useEffect } from 'react' -import { MbAvatar } from '../mb-avatar' - -export function ThreadDialog({ thread, excerpt, question }: ThreadDialogProps) { - const messages = useAsync(async () => - getMessages({ threadId: thread.threadId }) - ) - - // update url when dialog opens and closes - useEffect(() => { - const initialUrl = location.href - const threadUrl = `/${thread.chatbot.categories[0].category.name.toLowerCase().replaceAll('', '_').replaceAll('&', '_')}/${thread.threadId}` - history.pushState({}, threadUrl) - return () => { - history.pushState({}, initialUrl) - } - }) - - return ( - - - - - - - ) -} - -function ThreadDialogTrigger({ thread, excerpt, question }: ThreadDialogProps) { - return ( - -
div>div>button]:!hidden`)} - > -
- - -
- {question} - - by - - - -
-
- -
-
-
-
-
-
- ) -} - -interface ThreadDialogProps { - className?: string - handleOpen?: () => void - thread: Thread - excerpt: string - question: string -} diff --git a/apps/masterbots.ai/components/shared/thread-heading.tsx b/apps/masterbots.ai/components/shared/thread-heading.tsx new file mode 100644 index 00000000..5b48fabe --- /dev/null +++ b/apps/masterbots.ai/components/shared/thread-heading.tsx @@ -0,0 +1,58 @@ +import { Thread } from '@repo/mb-genql' +import { ShortMessage } from './thread-short-message' +import { MbAvatar } from './mb-avatar' +import { cn } from '@/lib/utils' + +export function ThreadHeading({ + thread, + response, + question +}: ThreadHeadingProps) { + return ( +
+
+ + +
+ {question} + + by + + +
+
+ + {response ? ( +
+ +
+ ) : null} +
+ ) +} + +interface ThreadHeadingProps { + thread: Thread + response?: string + question: string +} diff --git a/apps/masterbots.ai/components/shared/thread-dialog/thread-list.tsx b/apps/masterbots.ai/components/shared/thread-list.tsx similarity index 77% rename from apps/masterbots.ai/components/shared/thread-dialog/thread-list.tsx rename to apps/masterbots.ai/components/shared/thread-list.tsx index 2f34b275..8a580002 100644 --- a/apps/masterbots.ai/components/shared/thread-dialog/thread-list.tsx +++ b/apps/masterbots.ai/components/shared/thread-list.tsx @@ -7,7 +7,7 @@ import { useBrowse } from '@/hooks/use-browse' import { getBrowseThreads } from '@/services/hasura' import { ThreadDialog } from './thread-dialog' -export default function BrowseList({ initialThreads }: BrowseListProps) { +export default function ThreadList({ initialThreads }: ThreadListProps) { const { keyword, tab } = useBrowse() const [threads, setThreads] = useState(initialThreads) const [filteredThreads, setFilteredThreads] = @@ -68,24 +68,15 @@ export default function BrowseList({ initialThreads }: BrowseListProps) { }, [hasMore, loading, loadMore]) return ( -
+
{filteredThreads.map((thread: Thread, key) => ( - + ))}
) } -type BrowseListProps = { +type ThreadListProps = { initialThreads: Thread[] } - -const excerpt = - 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed id mauris rhoncus, imperdiet dui a, viverra eros. Nullam eget metus ante. Etiam maximus erat ut libero rutrum, non cursus sapien condimentum. Quisque ultricies suscipit augue eu aliquam. Donec fringilla tristique vestibulum.' -const question = 'why is the sky blue?' diff --git a/apps/masterbots.ai/components/browse/browse-chat-message.tsx b/apps/masterbots.ai/components/shared/thread-message.tsx similarity index 82% rename from apps/masterbots.ai/components/browse/browse-chat-message.tsx rename to apps/masterbots.ai/components/shared/thread-message.tsx index ce6f45e0..722b7e3b 100644 --- a/apps/masterbots.ai/components/browse/browse-chat-message.tsx +++ b/apps/masterbots.ai/components/shared/thread-message.tsx @@ -1,16 +1,11 @@ -// Inspired by Chatbot-UI and modified to fit the needs of this project -// @see https://github.com/mckaywrigley/chatbot-ui/blob/main/components/Chat/ChatcleanMessage.tsx - import type { Message } from 'ai' import remarkGfm from 'remark-gfm' import remarkMath from 'remark-math' -import Image from 'next/image' import type { Chatbot } from '@repo/mb-genql' -import { cleanPrompt, cn } from '@/lib/utils' +import { cn } from '@/lib/utils' import { CodeBlock } from '@/components/ui/codeblock' -import { MemoizedReactMarkdown } from '@/components/markdown' -import { IconOpenAI, IconUser } from '@/components/ui/icons' -import { ChatMessageActions } from '../c/chat-message-actions' +import { MemoizedReactMarkdown } from '@/components/shared/markdown' +import { cleanPrompt } from '@/lib/threads' export interface ChatMessageProps { message: Message diff --git a/apps/masterbots.ai/components/shared/thread-dialog/thread-excerpt.tsx b/apps/masterbots.ai/components/shared/thread-short-message.tsx similarity index 93% rename from apps/masterbots.ai/components/shared/thread-dialog/thread-excerpt.tsx rename to apps/masterbots.ai/components/shared/thread-short-message.tsx index 547a4c66..6ff0a501 100644 --- a/apps/masterbots.ai/components/shared/thread-dialog/thread-excerpt.tsx +++ b/apps/masterbots.ai/components/shared/thread-short-message.tsx @@ -1,7 +1,7 @@ import remarkGfm from 'remark-gfm' import remarkMath from 'remark-math' -import { MemoizedReactMarkdown } from '../../markdown' -import { CodeBlock } from '../../ui/codeblock' +import { MemoizedReactMarkdown } from './markdown' +import { CodeBlock } from '../ui/codeblock' export function ShortMessage({ content }: { content: string }) { return ( diff --git a/apps/masterbots.ai/hooks/use-thread.tsx b/apps/masterbots.ai/hooks/use-thread.tsx index cc8bb50a..5befd2d4 100644 --- a/apps/masterbots.ai/hooks/use-thread.tsx +++ b/apps/masterbots.ai/hooks/use-thread.tsx @@ -12,7 +12,7 @@ import { Message as AIMessage } from 'ai' import { uniqBy } from 'lodash' import toast from 'react-hot-toast' import { Chatbot, Message, Thread } from '@repo/mb-genql' -import { getAllUserMessagesAsStringArray } from '@/components/c/chat' +import { getAllUserMessagesAsStringArray } from '@/components/routes/c/chat' import { useSidebar } from './use-sidebar' import { useScroll } from 'framer-motion' import { useAtBottom } from './use-at-bottom' diff --git a/apps/masterbots.ai/lib/animation.ts b/apps/masterbots.ai/lib/animation.ts new file mode 100644 index 00000000..3cfcd9df --- /dev/null +++ b/apps/masterbots.ai/lib/animation.ts @@ -0,0 +1,34 @@ +// Easing function for smooth animation +export const easeInOutQuad = (t: number, b: number, c: number, d: number) => { + t /= d / 2 + if (t < 1) return (c / 2) * t * t + b + t-- + return (-c / 2) * (t * (t - 2) - 1) + b +} + +let animationFrameId: number +export const scrollToBottomOfElement = (element?: HTMLElement) => { + if (!element) return + const targetScroll = element.scrollHeight - element.clientHeight + const duration = 500 + const startTime = performance.now() + + const animateScroll = (currentTime: number) => { + const elapsed = currentTime - startTime + const position = easeInOutQuad( + elapsed, + element.scrollTop, + targetScroll - element.scrollTop, + duration + ) + element.scrollTop = position + + if (elapsed < duration) { + animationFrameId = requestAnimationFrame(animateScroll) + } else { + cancelAnimationFrame(animationFrameId) + } + } + + animationFrameId = requestAnimationFrame(animateScroll) +} diff --git a/apps/masterbots.ai/lib/threads.ts b/apps/masterbots.ai/lib/threads.ts new file mode 100644 index 00000000..fab3891e --- /dev/null +++ b/apps/masterbots.ai/lib/threads.ts @@ -0,0 +1,71 @@ +import type * as AI from 'ai' +import { Message } from '@repo/mb-genql' +import { type Message as AIMessage } from 'ai/react' + +export function createMessagePairs(messages: Message[] | AIMessage[]) { + const messagePairs: MessagePair[] = [] + + for (let i = 0; i < messages.length; i++) { + const message = messages[i] + + if (message.role === 'user') { + const userMessage = message + const chatGptMessages = [] + for (let j = i + 1; j < messages.length; j++) { + const chatGptMessage = findNextAssistantMessage(messages, j) + if (!chatGptMessage) { + break + } else { + chatGptMessages.push(chatGptMessage) + continue + } + } + messagePairs.push({ + userMessage, + chatGptMessage: chatGptMessages + }) + } + } + + return messagePairs +} + +const findNextAssistantMessage = ( + messages: Message[] | AIMessage[], + startIndex: number +) => { + if (messages[startIndex].role === 'assistant') { + return { + ...messages[startIndex], + content: cleanPrompt(messages[startIndex].content) + } + } + return null +} + +// From chat-message.tsx +export function cleanPrompt(str: string) { + const marker = ']. Then answer this question:' + const index = str.indexOf(marker) + let extracted = '' + + if (index !== -1) { + extracted = str.substring(index + marker.length) + } + // console.log('cleanPrompt', str, extracted, index) + return extracted || str +} + +export interface MessagePair { + userMessage: Message | AI.Message + chatGptMessage: Message[] +} + +export function convertMessage(message: Message) { + return { + id: message.messageId, + content: message.content, + createAt: message.createdAt, + role: message.role + } as AI.Message +} diff --git a/apps/masterbots.ai/lib/utils.ts b/apps/masterbots.ai/lib/utils.ts index 0783e965..ccacf839 100644 --- a/apps/masterbots.ai/lib/utils.ts +++ b/apps/masterbots.ai/lib/utils.ts @@ -1,5 +1,3 @@ -import { type Message as AIMessage } from 'ai/react' -import type { Message } from '@repo/mb-genql' import { clsx, ClassValue } from 'clsx' import { customAlphabet } from 'nanoid' import { twMerge } from 'tailwind-merge' @@ -70,61 +68,6 @@ export function extractBetweenMarkers( return str.substring(startIndex, endIndex).trim() } -// From browse-list.tsx -export function createMessagePairs(messages: Message[] | AIMessage[]) { - const messagePairs = [] - - for (let i = 0; i < messages.length; i++) { - const message = messages[i] - - if (message.role === 'user') { - const userMessage = message - const chatGptMessages = [] - for (let j = i + 1; j < messages.length; j++) { - const chatGptMessage = findNextAssistantMessage(messages, j) - if (!chatGptMessage) { - break - } else { - chatGptMessages.push(chatGptMessage) - continue - } - } - messagePairs.push({ - userMessage, - chatGptMessage: chatGptMessages - }) - } - } - - return messagePairs -} - -const findNextAssistantMessage = ( - messages: Message[] | AIMessage[], - startIndex: number -) => { - if (messages[startIndex].role === 'assistant') { - return { - ...messages[startIndex], - content: cleanPrompt(messages[startIndex].content) - } - } - return null -} - -// From chat-message.tsx -export function cleanPrompt(str: string) { - const marker = ']. Then answer this question:' - const index = str.indexOf(marker) - let extracted = '' - - if (index !== -1) { - extracted = str.substring(index + marker.length) - } - // console.log('cleanPrompt', str, extracted, index) - return extracted || str -} - export const readingTime = (messages: { content: string }[]) => { let contentGroup: any = [] @@ -141,41 +84,6 @@ export const readingTime = (messages: { content: string }[]) => { return time } -// Easing function for smooth animation -export const easeInOutQuad = (t: number, b: number, c: number, d: number) => { - t /= d / 2 - if (t < 1) return (c / 2) * t * t + b - t-- - return (-c / 2) * (t * (t - 2) - 1) + b -} - -let animationFrameId: number -export const scrollToBottomOfElement = (element?: HTMLElement) => { - if (!element) return - const targetScroll = element.scrollHeight - element.clientHeight - const duration = 500 - const startTime = performance.now() - - const animateScroll = (currentTime: number) => { - const elapsed = currentTime - startTime - const position = easeInOutQuad( - elapsed, - element.scrollTop, - targetScroll - element.scrollTop, - duration - ) - element.scrollTop = position - - if (elapsed < duration) { - animationFrameId = requestAnimationFrame(animateScroll) - } else { - cancelAnimationFrame(animationFrameId) - } - } - - animationFrameId = requestAnimationFrame(animateScroll) -} - export async function sleep(time: number) { return new Promise(resolve => setTimeout(resolve, time)) } diff --git a/apps/masterbots.ai/package.json b/apps/masterbots.ai/package.json index 2b68566e..ffd1e0f9 100644 --- a/apps/masterbots.ai/package.json +++ b/apps/masterbots.ai/package.json @@ -36,6 +36,7 @@ "@repo/mb-lib": "workspace:*", "@repo/mb-types": "workspace:*", "@supabase/ssr": "^0.1.0", + "@tanstack/react-query": "^5.29.0", "@vercel/analytics": "^1.1.1", "@vercel/og": "^0.5.20", "ai": "^2.2.25", diff --git a/apps/masterbots.ai/services/hasura/hasura.service.ts b/apps/masterbots.ai/services/hasura/hasura.service.ts index e871f0a2..35d85245 100644 --- a/apps/masterbots.ai/services/hasura/hasura.service.ts +++ b/apps/masterbots.ai/services/hasura/hasura.service.ts @@ -20,6 +20,7 @@ import { SaveNewMessageParams, UpsertUserParams } from './hasura.service.type' +import { createMessagePairs } from '@/lib/threads' function getHasuraClient({ jwt, adminSecret }: GetHasuraClientParams) { return createMbClient({ @@ -541,3 +542,8 @@ export async function getUser({ }) return user[0] } + +export async function getMessagePairs(threadId) { + const messages = await getMessages({ threadId }) + return createMessagePairs(messages) +} diff --git a/bun.lockb b/bun.lockb index 1fa6a41fe3eb1fa3a430c1ede964b8ca1bda38ff..21507a16aff89fa63ffe32468f12f44d1dea2a6a 100755 GIT binary patch delta 65661 zcmeFa33L?2`}aFNVPu9qBC-h>kR?dKh?rm?!zv)ML`6150t6EFYzQs{L=<3o*hzKr-h$y(DA_PSYiUNYZpKo_fM34X9ecyBMIrrRqoWnOyJ)f?O_`|rZLxfjwa;Re�`d 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 P%U27J3^#Y{!iN6?Xtu{8 delta 65195 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 Date: Mon, 8 Apr 2024 08:26:18 -0600 Subject: [PATCH 04/10] feat: ssr and layout improvements on u and b routes --- .../app/(browse)/[category]/page.tsx | 4 +- apps/masterbots.ai/app/(browse)/page.tsx | 6 +- apps/masterbots.ai/app/b/[id]/page.tsx | 18 ++-- apps/masterbots.ai/app/u/[slug]/page.tsx | 17 ++-- .../components/layout/header.tsx | 34 ++++---- .../components/routes/b/bot-details.tsx | 82 ------------------ .../routes/browse/browse-user-details.tsx | 50 ----------- .../routes/c/chat-layout-section.tsx | 2 +- .../{thread-list.tsx => chat-thread-list.tsx} | 0 ...thread-popup.tsx => chat-thread-popup.tsx} | 0 .../routes/c/thread-date-range-picker.tsx | 64 -------------- .../c/thread-panel/user-thread-panel.tsx | 3 +- .../{mb-avatar.tsx => account-avatar.tsx} | 7 +- .../components/shared/account-details.tsx | 72 +++++++++++++++ .../components/shared/thread-accordion.tsx | 9 +- .../components/shared/thread-dialog.tsx | 2 +- .../components/shared/thread-heading.tsx | 8 +- .../services/hasura/hasura.service.ts | 1 - bun.lockb | Bin 401040 -> 400664 bytes 19 files changed, 134 insertions(+), 245 deletions(-) delete mode 100644 apps/masterbots.ai/components/routes/b/bot-details.tsx delete mode 100644 apps/masterbots.ai/components/routes/browse/browse-user-details.tsx rename apps/masterbots.ai/components/routes/c/{thread-list.tsx => chat-thread-list.tsx} (100%) rename apps/masterbots.ai/components/routes/c/{thread-popup.tsx => chat-thread-popup.tsx} (100%) delete mode 100644 apps/masterbots.ai/components/routes/c/thread-date-range-picker.tsx rename apps/masterbots.ai/components/shared/{mb-avatar.tsx => account-avatar.tsx} (83%) create mode 100644 apps/masterbots.ai/components/shared/account-details.tsx diff --git a/apps/masterbots.ai/app/(browse)/[category]/page.tsx b/apps/masterbots.ai/app/(browse)/[category]/page.tsx index 2320051e..424e450c 100644 --- a/apps/masterbots.ai/app/(browse)/[category]/page.tsx +++ b/apps/masterbots.ai/app/(browse)/[category]/page.tsx @@ -1,4 +1,4 @@ -import BrowseList from '@/components/shared/thread-list' +import ThreadList from '@/components/shared/thread-list' import { BrowseCategoryTabs } from '@/components/routes/browse/browse-category-tabs' import { BrowseSearchInput } from '@/components/routes/browse/browse-search-input' import { getBrowseThreads, getCategories } from '@/services/hasura' @@ -30,7 +30,7 @@ export default async function BrowseCategoryPage({ initialCategory={params.category} /> - +
) } diff --git a/apps/masterbots.ai/app/(browse)/page.tsx b/apps/masterbots.ai/app/(browse)/page.tsx index 0a502c0e..8de81e88 100644 --- a/apps/masterbots.ai/app/(browse)/page.tsx +++ b/apps/masterbots.ai/app/(browse)/page.tsx @@ -1,10 +1,8 @@ -import BrowseList from '@/components/shared/thread-list' +import ThreadList from '@/components/shared/thread-list' import { BrowseCategoryTabs } from '@/components/routes/browse/browse-category-tabs' import { BrowseSearchInput } from '@/components/routes/browse/browse-search-input' import { getBrowseThreads, getCategories } from '@/services/hasura' -export const revalidate = 3600 // revalidate the data at most every hour - export default async function BrowsePage() { const categories = await getCategories() const threads = await getBrowseThreads({ @@ -15,7 +13,7 @@ export default async function BrowsePage() {
- +
) } diff --git a/apps/masterbots.ai/app/b/[id]/page.tsx b/apps/masterbots.ai/app/b/[id]/page.tsx index 4a1efe3c..cdcc0da7 100644 --- a/apps/masterbots.ai/app/b/[id]/page.tsx +++ b/apps/masterbots.ai/app/b/[id]/page.tsx @@ -1,8 +1,7 @@ import { getChatbot, getBrowseThreads } from '@/services/hasura' import { botNames } from '@/lib/bots-names' -import BotDetails from '@/components/routes/b/bot-details' - -const PAGE_SIZE = 50 +import ThreadList from '@/components/shared/thread-list' +import AccountDetails from '@/components/shared/account-details' export default async function BotThreadsPage({ params @@ -20,12 +19,21 @@ export default async function BotThreadsPage({ threads = await getBrowseThreads({ chatbotName: botNames.get(params.id), - limit: PAGE_SIZE + limit: 50 }) return (
- + +
+ +
) } diff --git a/apps/masterbots.ai/app/u/[slug]/page.tsx b/apps/masterbots.ai/app/u/[slug]/page.tsx index 193ae474..48aa257e 100644 --- a/apps/masterbots.ai/app/u/[slug]/page.tsx +++ b/apps/masterbots.ai/app/u/[slug]/page.tsx @@ -1,7 +1,6 @@ import { getBrowseThreads, getUserInfoFromBrowse } from '@/services/hasura' -import BrowseUserDetails from '@/components/routes/browse/browse-user-details' - -const PAGE_SIZE = 50 +import ThreadList from '@/components/shared/thread-list' +import AccountDetails from '@/components/shared/account-details' export default async function BotThreadsPage({ params @@ -12,11 +11,19 @@ export default async function BotThreadsPage({ if (!user) return
No user found.
const threads = await getBrowseThreads({ slug: params.slug, - limit: PAGE_SIZE + limit: 50 }) return (
- + +
+ +
) } diff --git a/apps/masterbots.ai/components/layout/header.tsx b/apps/masterbots.ai/components/layout/header.tsx index bd35304e..2ed83d03 100644 --- a/apps/masterbots.ai/components/layout/header.tsx +++ b/apps/masterbots.ai/components/layout/header.tsx @@ -15,22 +15,24 @@ export async function Header() { const jwt = cookies().get('hasuraJwt')?.value || '' return ( -
-
- - - - - -
-
- {user && !isTokenExpired(jwt) ? ( - - ) : ( - - )} +
+
+
+ + + + + +
+
+ {user && !isTokenExpired(jwt) ? ( + + ) : ( + + )} +
) diff --git a/apps/masterbots.ai/components/routes/b/bot-details.tsx b/apps/masterbots.ai/components/routes/b/bot-details.tsx deleted file mode 100644 index 95267e16..00000000 --- a/apps/masterbots.ai/components/routes/b/bot-details.tsx +++ /dev/null @@ -1,82 +0,0 @@ -import type { Chatbot } from '@repo/mb-genql' -import Image from 'next/image' -import Link from 'next/link' -import { Separator } from '../../ui/separator' - -export default function BrowseChatbotDetails({ - chatbot -}: { - chatbot?: Chatbot -}) { - return ( -
-
-
-
{chatbot.name}
- -
- {chatbot.categories[0].category.name}. -
-
-
- {chatbot.description ?
{chatbot.description}
: ''} -
-
- Threads:{' '} - - {chatbot.threads.length ?? 1} - - {/*
- Views: 0 -
*/} - {/*
- Read time:{' '} - - {readingTime(messages)} min - -
*/} -
-
-
-
-
- - Chat with {chatbot.name} > - - {/*
- - 0 - - 0 - - -
*/} -
-
-
- {chatbot.avatar -
-
-
- ) -} diff --git a/apps/masterbots.ai/components/routes/browse/browse-user-details.tsx b/apps/masterbots.ai/components/routes/browse/browse-user-details.tsx deleted file mode 100644 index eccfd76a..00000000 --- a/apps/masterbots.ai/components/routes/browse/browse-user-details.tsx +++ /dev/null @@ -1,50 +0,0 @@ -'use client' - -import type { User } from '@repo/mb-genql' -import Image from 'next/image' -import { useEffect, useState } from 'react' -import { getBrowseThreads } from '@/services/hasura' -import { Separator } from '../../ui/separator' - -export default function BrowseChatbotDetails({ user }: { user?: User | null }) { - const [threadNum, setThreadNum] = useState(0) - const getThreadByUserName = async () => { - const threads = await getBrowseThreads({ - slug: user.slug - }) - setThreadNum(threads.length) - } - useEffect(() => { - getThreadByUserName() - }, []) - return ( -
-
-
-
- {user.username.replace('_', ' ')} -
- - -
-
- Threads: {threadNum ?? 1} -
-
-
-
- {user.username -
-
-
- ) -} diff --git a/apps/masterbots.ai/components/routes/c/chat-layout-section.tsx b/apps/masterbots.ai/components/routes/c/chat-layout-section.tsx index 7abb0225..0b062805 100644 --- a/apps/masterbots.ai/components/routes/c/chat-layout-section.tsx +++ b/apps/masterbots.ai/components/routes/c/chat-layout-section.tsx @@ -2,7 +2,7 @@ import * as React from 'react' import { useThread } from '@/hooks/use-thread' -import { ThreadPopup } from './thread-popup' +import { ThreadPopup } from './chat-thread-popup' export function ChatLayoutSection({ children }: { children: React.ReactNode }) { const { sectionRef, isOpenPopup } = useThread() diff --git a/apps/masterbots.ai/components/routes/c/thread-list.tsx b/apps/masterbots.ai/components/routes/c/chat-thread-list.tsx similarity index 100% rename from apps/masterbots.ai/components/routes/c/thread-list.tsx rename to apps/masterbots.ai/components/routes/c/chat-thread-list.tsx diff --git a/apps/masterbots.ai/components/routes/c/thread-popup.tsx b/apps/masterbots.ai/components/routes/c/chat-thread-popup.tsx similarity index 100% rename from apps/masterbots.ai/components/routes/c/thread-popup.tsx rename to apps/masterbots.ai/components/routes/c/chat-thread-popup.tsx diff --git a/apps/masterbots.ai/components/routes/c/thread-date-range-picker.tsx b/apps/masterbots.ai/components/routes/c/thread-date-range-picker.tsx deleted file mode 100644 index 26abf56e..00000000 --- a/apps/masterbots.ai/components/routes/c/thread-date-range-picker.tsx +++ /dev/null @@ -1,64 +0,0 @@ -'use client' - -import * as React from 'react' -import { CalendarIcon } from '@radix-ui/react-icons' -import { addDays, format } from 'date-fns' -import type { DateRange } from 'react-day-picker' -import { cn } from '@/lib/utils' -import { Button } from '@/components/ui/button' -import { Calendar } from '@/components/ui/calendar' -import { - Popover, - PopoverContent, - PopoverTrigger -} from '@/components/ui/popover' - -export function DateRangePicker({ - className -}: React.HTMLAttributes) { - const [date, setDate] = React.useState({ - from: new Date(2023, 0, 20), - to: addDays(new Date(2023, 0, 20), 20) - }) - - return ( -
- - - - - - - - -
- ) -} diff --git a/apps/masterbots.ai/components/routes/c/thread-panel/user-thread-panel.tsx b/apps/masterbots.ai/components/routes/c/thread-panel/user-thread-panel.tsx index e4ddc093..e6a8e32f 100644 --- a/apps/masterbots.ai/components/routes/c/thread-panel/user-thread-panel.tsx +++ b/apps/masterbots.ai/components/routes/c/thread-panel/user-thread-panel.tsx @@ -3,12 +3,13 @@ import type { Thread } from '@repo/mb-genql' import React, { useEffect, useRef, useState } from 'react' import { ChatSearchInput } from '@/components/routes/c/chat-search-input' -import ThreadList from '@/components/routes/c/thread-list' + import { useSidebar } from '@/hooks/use-sidebar' import { useThread } from '@/hooks/use-thread' import { getThreads } from '@/services/hasura' import { useGlobalStore } from '@/hooks/use-global-store' import ChatChatbotDetails from '../chat-chatbot-details' +import ThreadList from '../chat-thread-list' const PAGE_SIZE = 20 diff --git a/apps/masterbots.ai/components/shared/mb-avatar.tsx b/apps/masterbots.ai/components/shared/account-avatar.tsx similarity index 83% rename from apps/masterbots.ai/components/shared/mb-avatar.tsx rename to apps/masterbots.ai/components/shared/account-avatar.tsx index 0072fc95..6378bdda 100644 --- a/apps/masterbots.ai/components/shared/mb-avatar.tsx +++ b/apps/masterbots.ai/components/shared/account-avatar.tsx @@ -3,7 +3,7 @@ import Image from 'next/image' import Link from 'next/link' import { IconUser } from '../ui/icons' -export function MbAvatar({ href, alt, src }: MbAvatarProp) { +export function AccountAvatar({ href, alt, src, size = 32 }: MbAvatarProp) { return ( ) : ( @@ -31,4 +31,5 @@ interface MbAvatarProp { href: string alt: string src?: string + size?: number } diff --git a/apps/masterbots.ai/components/shared/account-details.tsx b/apps/masterbots.ai/components/shared/account-details.tsx new file mode 100644 index 00000000..6e3c6b61 --- /dev/null +++ b/apps/masterbots.ai/components/shared/account-details.tsx @@ -0,0 +1,72 @@ +import Image from 'next/image' +import Link from 'next/link' +import { Separator } from '../ui/separator' + +export default function AccountDetails({ + alt, + avatar, + username, + href, + threadNum = 0, + chatbotName, + description +}: AccountDetailsProps) { + if (!username && !chatbotName) + throw new Error('You must pass username or chatbotName') + + return ( +
+
+
+ {username +
+ +
+
{username || chatbotName}
+ + {/*
+ {chatbot.categories[0].category.name}. +
*/} +
+
+ {description ?
{description}
: ''} +
+
+ +
+
+ Threads: {threadNum ?? 0} +
+
+ {chatbotName ? ( + + Chat with {chatbotName} > + + ) : null} +
+
+
+
+
+ ) +} + +interface AccountDetailsProps { + alt?: string + avatar: string + username?: string + href: string + threadNum?: number + chatbotName?: string + description?: string +} diff --git a/apps/masterbots.ai/components/shared/thread-accordion.tsx b/apps/masterbots.ai/components/shared/thread-accordion.tsx index 9f8b16d2..b3488f37 100644 --- a/apps/masterbots.ai/components/shared/thread-accordion.tsx +++ b/apps/masterbots.ai/components/shared/thread-accordion.tsx @@ -23,11 +23,7 @@ export function ThreadAccordion({ clientFetch = false }: ThreadAccordionProps) { // initalMessages is coming from server ssr on load. the rest of messages on demand on mount - const { - data: pairs, - isLoading, - error - } = useQuery({ + const { data: pairs, error } = useQuery({ queryKey: [`messages-${thread.threadId}`], queryFn: () => getMessagePairs(thread.threadId), initialData: initialMessagePairs, @@ -50,7 +46,8 @@ export function ThreadAccordion({ if (error) return
There was an error loading thread messages
// if no initial message and still loading show loading message - if (!pairs?.length && isLoading) return
Loading thread messages ...
+ // NOTE: its fast and transitions in. testing without this + if (!pairs?.length) return null console.log(pairs.map((_p, key) => `pair-${key}`)) return ( diff --git a/apps/masterbots.ai/components/shared/thread-dialog.tsx b/apps/masterbots.ai/components/shared/thread-dialog.tsx index f2a414e6..cf0a5f6a 100644 --- a/apps/masterbots.ai/components/shared/thread-dialog.tsx +++ b/apps/masterbots.ai/components/shared/thread-dialog.tsx @@ -24,7 +24,7 @@ export function ThreadDialog({ thread }: ThreadDialogProps) { diff --git a/apps/masterbots.ai/components/shared/thread-heading.tsx b/apps/masterbots.ai/components/shared/thread-heading.tsx index 5b48fabe..d061ffc9 100644 --- a/apps/masterbots.ai/components/shared/thread-heading.tsx +++ b/apps/masterbots.ai/components/shared/thread-heading.tsx @@ -1,6 +1,6 @@ import { Thread } from '@repo/mb-genql' import { ShortMessage } from './thread-short-message' -import { MbAvatar } from './mb-avatar' +import { AccountAvatar } from './account-avatar' import { cn } from '@/lib/utils' export function ThreadHeading({ @@ -19,7 +19,7 @@ export function ThreadHeading({ 'relative flex items-center font-normal md:text-lg transition-all w-full gap-3 pr-4' )} > - by -
diff --git a/apps/masterbots.ai/services/hasura/hasura.service.ts b/apps/masterbots.ai/services/hasura/hasura.service.ts index 35d85245..0c4184a1 100644 --- a/apps/masterbots.ai/services/hasura/hasura.service.ts +++ b/apps/masterbots.ai/services/hasura/hasura.service.ts @@ -507,7 +507,6 @@ export async function getUserInfoFromBrowse(slug: string) { user: { username: true, profilePicture: true, - name: true, __args: { where: { slug: { diff --git a/bun.lockb b/bun.lockb index 21507a16aff89fa63ffe32468f12f44d1dea2a6a..ed82c55c66105cc7791576ada21be7dc491c6a10 100755 GIT binary patch delta 49751 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%0ADgE)7AmLYf4mE>NG4eV8*ZaRScsy7_C1C z_#&fY`2t*RJ~3*>0{`_bAEdwH8a2DF(RF-b-gF=t4>H{_(KAU`nshgQHKtR;Jv(+? zPIs5dQo0=T(;#2P>Y6JXNUJfZaxU}arf#*Cgwi&3J!2-^=8KAYnFLp<&|~-C->2~_ z-GhUq^T3?$y;uQ*L(cK5vD)2Q`gv)T~kS_{HYZ5V|YZL=9z8+G&!9`rdKBYGQ_Q`n+dG z52Fu{b8vYM4t%iay<3O$(%f&}NK?>t!8}NU78Tbz>s7C>ckFEP+_Nzx;KXgzf}3(; zA~|&H$zusWj_t22k778qi#e&hZzF*g+{8*>FO95NzpQO^OCIo=n(0(*g-O6fuP0q) z(q%v2clfoZy_+s)wA+j$LCX(Fp!K=T&3D)DyygC@ZAOga_rydIQ*3g-70K6r_@zCx zInPb<%?s(i$f}j|onr@0yWyQP-+Z5NIYn%~Ig{?oj_ybbjn!y()u%lT>N~4>aB=Ew zmJO$V!%3i-opRcn|3yK|@L(2AwcJlNdxq1qv(2x=eIw}S)Dga@^4o2GE!OUGABCH zl*;4^;xvWVz1wAy(F8L<#zI@N6BqsICg((`bbd~S>TOFGx%&Rd-)*2fJdZjj!lb=( zzT3XFt5$1GcbZV`QS-!dU!-X`mJ)5QkDA-4bMy`l(mC>E-O3f0t{(b02en{}!qoXo zy3kQhOZkf(J?XSi1G%iHO`!sF9(NX~*sAjf?Yjd**V(YA(IpOs6Kqt(Xja z*<505TfDo)nGsJ79dWo=waHU1kH>e!Xf-)?rthse53TrC&2-xNvq>J$p!7Gh$1~Y; z_!AVt(Z-A71?t`tFfW+VGQlK;(D6*IF~RW&)?$K5o5K#J#>C6W9^;Hg%PWkA-3tE5 zI8~s2Z`F)y8}nLbXX(Wz=2wkrL^~ZC-JhAQ{;GTALYa#VzsmNw8@im2?QvU__0E|u z7zWK@WnzwI_$m~4y4NPKRSJ%I^XPwW?MSngNfD~EZ96l(>DPF4T8cspc;?X~%ZhRSJE zVXSTUd*;RIT6uRlr$XBGmu^h_`11po8|Czfr`WTm^c}3CG0!>+z^+jZ%VfLe9J!qE zFMZ}|^{i=~#RTbZmTOl1k6m-cKGAZfNg3*c!pi?;nEp?(&HgFNe<#f;rt zVi?ms#pW!jUE%xHmz%wgm>R@<*na4#J7%mNdpYJ6lRT4a)-e)j1MtN4SHD>0-9y7K zCkW#gq-qk=C%fM8q?6w+y&O~b1+!)*z30rl!Wr^koBh8^RXEG+_*DJp$@Q`vHxQr?3DP2SZ5W8uC<46L+OP-7hT$W_j8laUhaimz8?v6vsj|o z@XbAsUwG+qg2}{a8Em|0(!h&5n7rueUkDA|E|! zuXDMr`BvTCCtodDmE)VTN#b%l$5fwBhb|?7o|ddCS>-@rOO;=O34)JP@`=$V@9ecr zt5#VXtE;QMFW+u5=X0eyYQ9*_HS2@<)cUOXX}<4xi;R6vaS?}GA9uB=7aBiqs%I0U za*pm9Sz+SYcJ;|kk4xArtTGV`>EE@c-onC6J+=3ndlvd4TeR5kY`OOPQ@4J9+Yd81 zOj|BC+Z~C~Ca-&)xXO1=ImRtUuzGB^SDC|3hUFFo^Sx}Um`*|+&25GztCLD@l`cnEcS(Ysva~)7yG(< z>K`=ME%7z1-SnXI@MtntRe1dUUf~}M{Ju9O=xG-v%rdi=_?lm74`erC#osgUE};u+ z@C&56slMmX8v~ZdoURwFWH5^aVl=|-nmtzg>E|oz>Sd4ZZes3Ya@NIUv}Jq7 z{kHpZf=2&OYgZmt#gW9{yv#!mLF90Ofq=4*tbl?DQCXuzG>TU|f=5=77(9{~gNfpL ziw7nODjG`!MPft{1QC%SB5I-r#VbhG>yw}c{o*Pyy57*Civtb^0(h&A;+d?>DETb_ui9;GxO3chrb>xEC&=wEJ6-mn4SO62?y zJx<>61;JY&lTgGGJaWWR$SKZy>5^ML9P0jkfHA-a z@;FjD3dAf$ijAEyY;wZwCiNuy&U+f z(7O}uXQK>XCX?S%=;=Yk5NrV_d^GLGl!76L86VJ7h$xSu03a08bTqC<&Tv}Sy9AX_ z)fIx48VD*3Vgp8cqNWe5+>pc_HOnIEV`T=T&PjcHmy(x(Kf^?UhvC9?l2}GFu8^y2 zkL0FBm8%*GSPSXhquyv-?XPh}V-qGVlK$@OIAny%@2?14}0X!HgMlaJE2y-V9R0Hq-k<*9;N zW&ovJn|Gu+8%2@Ed?UJOz3)-nH~K+z6z>!SlFxl^3bDZtqh~FC`&>O;j#G*Q04wZm z^sXK!gl`?%5a3B_uHN@}Sg>z)znY2FB_m-EES7;~IzOQG8*wPdXDzk8%gO$h9~%*A zSB`S9I$dQBoH`B80Q>y3rVsAwE6u>E6K3;sCX_9cnq|VsA)@V+unGGzr_th-_L}c$ zFJ554S@xa}a1;?0$YY$Pln0#8H%DrBovbsjuCK%JA@r|Iu^*?`rU?sdG%8V!ne4K} ze%hc%^nR8YX8zkF?jmS+cL+9`%CkftX~tuI4q(jbHQOw{D-FkrTR^b(3C+?fb(^l? zyaBldctiv}d$Sn$VWc3)F;?aEvt7U1I1eGHKZ@-L!STZoLALc1L#vm*)^6(vt#A=< zngZD?T0u@FYUcS_%u38nvE=}qn6&u zM(H99kc_d3i_a$?>zIrpl^Rp^rFq#<#Yig37W=x|y~5#!3{2HLs=I%P@#o(Lz7L=J zkw*^nZ7dDS0elw4=iqeO%S@2-(>s4VGqL@Hfh<3TYVh)AT@LItx(P1U1L51$d5bt! z`of&g<)5h|CR@cWw19wcP7#O!m#J(EBImmRU}5aX$95OK^J5%>zjnaKwgDRpE})`2 zD`Ol34o_q~NKTL%wXz_aTuib9ARIBau<6Z@{VK!58AL}}xP2)!mxX>7vb& z!IoT%{V#;g2)Ej|P8LoOb2?%4T`snSX#il8824H3a2xD+DWz>=Qffv!w_$(#gIX4dK3W@F>Q^9o@cnnJM0SFv6%%r0 z@BCzGAU|NlzV!ySRfYZT7T}G!56JHC z_LNhIWeWlXTQ;Yf%(B|DY~~-l-Ul4Bht6YqLl6+o&5oZ!+tHnDF2GnKDXjwIXHv^OXuhBUWulMgETCcG zMZ=BUR#1CpQs3RsH3h5Ta7hI+GMu9Ji!D_qUUHZ!2botNVU~=GF8e? z4p5{SK~dkSkm|%ua&Abik<*tx^5kC7dBMrG-VRqq2+1`DpIqRdTUd=*V+ z{O3~d^JorG-pkL15`TcYKPd>k$ezTfe`z2`JzFoUItxg5z(|m2E4x(UOBudIOeCFS;?M;<7WK zrWmM7R5dZJ=j-HK4ywvhy(IA$5W2}3XxsjSDq#JpL+L`ze`=^^*ft~COU1oRqPd6h zlt?M8G3IRa^Hf|4J~3jpr%qd09{ubUN0UX>a8$x}ayyO2Ko)O4$8RVO$+0zeOy8&Y z@4p*o?j>7}9AWvkrcaOHL#R+d*ur(aQQD-a#I^rHS$X6l<^U=^g5&#!pHny}1x$T) za}8M?MPBzbTqX@T3Z65l^qAPvNWZ=i%KCA|^jSdr<@oTBYjD7d{GZPJz`mPfArGRD zrwQm0gaCn-&&bb7PII3F3~C{Y$559Y0{}V(HJb9K}eg$+`+=pl*#fDU>~``>lb-|Al1r^cq;p!{31}64gOK5bo_h%$)R1l`u zSQUzXBWT<{KXcv90sz66f?lUg%gvJ{KXN71aGA zig$8S;B48GT|N8x|@pTGt(0P#mG@T6{jnY{bmB#7Tud Date: Mon, 8 Apr 2024 08:32:58 -0600 Subject: [PATCH 05/10] fix: correct thread number --- apps/masterbots.ai/app/b/[id]/page.tsx | 1 + apps/masterbots.ai/app/u/[slug]/page.tsx | 1 + 2 files changed, 2 insertions(+) diff --git a/apps/masterbots.ai/app/b/[id]/page.tsx b/apps/masterbots.ai/app/b/[id]/page.tsx index cdcc0da7..88ea62e5 100644 --- a/apps/masterbots.ai/app/b/[id]/page.tsx +++ b/apps/masterbots.ai/app/b/[id]/page.tsx @@ -30,6 +30,7 @@ export default async function BotThreadsPage({ chatbotName={chatbot.name} avatar={chatbot.avatar} description={chatbot.description} + threadNum={threads.length} />
diff --git a/apps/masterbots.ai/app/u/[slug]/page.tsx b/apps/masterbots.ai/app/u/[slug]/page.tsx index 48aa257e..ebc5d5b5 100644 --- a/apps/masterbots.ai/app/u/[slug]/page.tsx +++ b/apps/masterbots.ai/app/u/[slug]/page.tsx @@ -20,6 +20,7 @@ export default async function BotThreadsPage({ alt={user.username} username={user.username} avatar={user.profilePicture || ''} + threadNum={threads.length} //TODO: get total number of thread. not the filter one />
From a103a9040be2488cd5ed83ae16791ad02156779b Mon Sep 17 00:00:00 2001 From: Gabo Esquivel Date: Mon, 8 Apr 2024 09:23:14 -0600 Subject: [PATCH 06/10] feat: ssr and layout improvements on u and b routes --- .../app/(browse)/[category]/[threadId]/page.tsx | 7 ++++++- .../app/(browse)/[category]/page.tsx | 4 ++-- apps/masterbots.ai/app/(browse)/page.tsx | 5 ++--- apps/masterbots.ai/app/b/[id]/page.tsx | 6 +++--- apps/masterbots.ai/app/u/[slug]/page.tsx | 2 +- .../components/shared/thread-list.tsx | 15 ++++++++++++--- 6 files changed, 26 insertions(+), 13 deletions(-) diff --git a/apps/masterbots.ai/app/(browse)/[category]/[threadId]/page.tsx b/apps/masterbots.ai/app/(browse)/[category]/[threadId]/page.tsx index 4a3d0699..c6e988b1 100644 --- a/apps/masterbots.ai/app/(browse)/[category]/[threadId]/page.tsx +++ b/apps/masterbots.ai/app/(browse)/[category]/[threadId]/page.tsx @@ -1,10 +1,13 @@ -import { getMessagePairs, getThread } from '@/services/hasura' +import { getCategories, getMessagePairs, getThread } from '@/services/hasura' import type { ChatPageProps } from '@/app/c/[chatbot]/[threadId]/page' import Shortlink from '@/components/routes/browse/shortlink-button' import { ThreadAccordion } from '@/components/shared/thread-accordion' +import { BrowseCategoryTabs } from '@/components/routes/browse/browse-category-tabs' +import { BrowseSearchInput } from '@/components/routes/browse/browse-search-input' export default async function ThreadLandingPage({ params }: ChatPageProps) { + const categories = await getCategories() const thread = await getThread({ threadId: params.threadId }) @@ -12,6 +15,8 @@ export default async function ThreadLandingPage({ params }: ChatPageProps) { return (
+ + +
- +
) } diff --git a/apps/masterbots.ai/app/(browse)/page.tsx b/apps/masterbots.ai/app/(browse)/page.tsx index 8de81e88..3b492380 100644 --- a/apps/masterbots.ai/app/(browse)/page.tsx +++ b/apps/masterbots.ai/app/(browse)/page.tsx @@ -8,12 +8,11 @@ export default async function BrowsePage() { const threads = await getBrowseThreads({ limit: 50 }) - return ( -
+
- +
) } diff --git a/apps/masterbots.ai/app/b/[id]/page.tsx b/apps/masterbots.ai/app/b/[id]/page.tsx index 88ea62e5..99a3c982 100644 --- a/apps/masterbots.ai/app/b/[id]/page.tsx +++ b/apps/masterbots.ai/app/b/[id]/page.tsx @@ -16,9 +16,9 @@ export default async function BotThreadsPage({ threads: true }) if (!chatbot) throw new Error(`Chatbot ${botNames.get(params.id)} not found`) - + const chatbotName = botNames.get(params.id) threads = await getBrowseThreads({ - chatbotName: botNames.get(params.id), + chatbotName, limit: 50 }) @@ -33,7 +33,7 @@ export default async function BotThreadsPage({ threadNum={threads.length} />
- +
) diff --git a/apps/masterbots.ai/app/u/[slug]/page.tsx b/apps/masterbots.ai/app/u/[slug]/page.tsx index ebc5d5b5..f2608f77 100644 --- a/apps/masterbots.ai/app/u/[slug]/page.tsx +++ b/apps/masterbots.ai/app/u/[slug]/page.tsx @@ -23,7 +23,7 @@ export default async function BotThreadsPage({ threadNum={threads.length} //TODO: get total number of thread. not the filter one />
- +
) diff --git a/apps/masterbots.ai/components/shared/thread-list.tsx b/apps/masterbots.ai/components/shared/thread-list.tsx index 8a580002..d5121b58 100644 --- a/apps/masterbots.ai/components/shared/thread-list.tsx +++ b/apps/masterbots.ai/components/shared/thread-list.tsx @@ -7,8 +7,11 @@ import { useBrowse } from '@/hooks/use-browse' import { getBrowseThreads } from '@/services/hasura' import { ThreadDialog } from './thread-dialog' -export default function ThreadList({ initialThreads }: ThreadListProps) { - const { keyword, tab } = useBrowse() +export default function ThreadList({ + initialThreads, + filter +}: ThreadListProps) { + const { keyword } = useBrowse() const [threads, setThreads] = useState(initialThreads) const [filteredThreads, setFilteredThreads] = useState(initialThreads) @@ -22,7 +25,7 @@ export default function ThreadList({ initialThreads }: ThreadListProps) { setLoading(true) const moreThreads = await getBrowseThreads({ - categoryId: tab, + ...filter, offset: filteredThreads.length, limit: 50 }) @@ -79,4 +82,10 @@ export default function ThreadList({ initialThreads }: ThreadListProps) { type ThreadListProps = { initialThreads: Thread[] + filter: { + categoryId?: number + userId?: string + chatbotName?: string + slug?: string + } } From 090356ba71615988bb6d4fd6b7311572ebf6b96e Mon Sep 17 00:00:00 2001 From: Gabo Esquivel Date: Mon, 8 Apr 2024 09:31:40 -0600 Subject: [PATCH 07/10] feat: global categories nav --- apps/masterbots.ai/app/(browse)/[category]/page.tsx | 2 +- apps/masterbots.ai/app/(browse)/page.tsx | 2 +- apps/masterbots.ai/app/b/[id]/page.tsx | 9 +++++++-- apps/masterbots.ai/app/u/[slug]/page.tsx | 13 +++++++++++-- 4 files changed, 20 insertions(+), 6 deletions(-) diff --git a/apps/masterbots.ai/app/(browse)/[category]/page.tsx b/apps/masterbots.ai/app/(browse)/[category]/page.tsx index 97f17749..c17fea61 100644 --- a/apps/masterbots.ai/app/(browse)/[category]/page.tsx +++ b/apps/masterbots.ai/app/(browse)/[category]/page.tsx @@ -24,7 +24,7 @@ export default async function BrowseCategoryPage({ }) return ( -
+
+
diff --git a/apps/masterbots.ai/app/b/[id]/page.tsx b/apps/masterbots.ai/app/b/[id]/page.tsx index 99a3c982..1bbc32ec 100644 --- a/apps/masterbots.ai/app/b/[id]/page.tsx +++ b/apps/masterbots.ai/app/b/[id]/page.tsx @@ -1,13 +1,16 @@ -import { getChatbot, getBrowseThreads } from '@/services/hasura' +import { getChatbot, getBrowseThreads, getCategories } from '@/services/hasura' import { botNames } from '@/lib/bots-names' import ThreadList from '@/components/shared/thread-list' import AccountDetails from '@/components/shared/account-details' +import { BrowseCategoryTabs } from '@/components/routes/browse/browse-category-tabs' +import { BrowseSearchInput } from '@/components/routes/browse/browse-search-input' export default async function BotThreadsPage({ params }: { params: { id: string } }) { + const categories = await getCategories() let chatbot, threads chatbot = await getChatbot({ @@ -23,7 +26,9 @@ export default async function BotThreadsPage({ }) return ( -
+
+ + No user found.
const threads = await getBrowseThreads({ @@ -14,7 +21,9 @@ export default async function BotThreadsPage({ limit: 50 }) return ( -
+
+ + Date: Mon, 8 Apr 2024 09:40:26 -0600 Subject: [PATCH 08/10] chore: move shared components --- .../app/(browse)/[category]/[threadId]/page.tsx | 10 +++++----- apps/masterbots.ai/app/(browse)/[category]/page.tsx | 11 ++++------- apps/masterbots.ai/app/(browse)/page.tsx | 8 ++++---- apps/masterbots.ai/app/b/[id]/page.tsx | 8 ++++---- apps/masterbots.ai/app/u/[slug]/page.tsx | 8 ++++---- .../browse-input.tsx} | 2 +- .../category-tabs/category-link.tsx} | 2 +- .../category-tabs/category-tabs.tsx} | 8 ++++---- .../components/shared/category-tabs/index.ts | 1 + .../{routes/browse => shared}/shortlink-button.tsx | 0 10 files changed, 28 insertions(+), 30 deletions(-) rename apps/masterbots.ai/components/{routes/browse/browse-search-input.tsx => shared/browse-input.tsx} (97%) rename apps/masterbots.ai/components/{routes/browse/browse-category-link.tsx => shared/category-tabs/category-link.tsx} (97%) rename apps/masterbots.ai/components/{routes/browse/browse-category-tabs.tsx => shared/category-tabs/category-tabs.tsx} (91%) create mode 100644 apps/masterbots.ai/components/shared/category-tabs/index.ts rename apps/masterbots.ai/components/{routes/browse => shared}/shortlink-button.tsx (100%) diff --git a/apps/masterbots.ai/app/(browse)/[category]/[threadId]/page.tsx b/apps/masterbots.ai/app/(browse)/[category]/[threadId]/page.tsx index c6e988b1..bd44bb9b 100644 --- a/apps/masterbots.ai/app/(browse)/[category]/[threadId]/page.tsx +++ b/apps/masterbots.ai/app/(browse)/[category]/[threadId]/page.tsx @@ -1,10 +1,10 @@ import { getCategories, getMessagePairs, getThread } from '@/services/hasura' import type { ChatPageProps } from '@/app/c/[chatbot]/[threadId]/page' -import Shortlink from '@/components/routes/browse/shortlink-button' +import Shortlink from '@/components/shared/shortlink-button' import { ThreadAccordion } from '@/components/shared/thread-accordion' -import { BrowseCategoryTabs } from '@/components/routes/browse/browse-category-tabs' -import { BrowseSearchInput } from '@/components/routes/browse/browse-search-input' +import { CategoryTabs } from '@/components/shared/category-tabs/category-tabs' +import { BrowseInput } from '@/components/shared/browse-input' export default async function ThreadLandingPage({ params }: ChatPageProps) { const categories = await getCategories() @@ -15,8 +15,8 @@ export default async function ThreadLandingPage({ params }: ChatPageProps) { return (
- - + + - - + +
) diff --git a/apps/masterbots.ai/app/(browse)/page.tsx b/apps/masterbots.ai/app/(browse)/page.tsx index 1511a84e..9c39ccbf 100644 --- a/apps/masterbots.ai/app/(browse)/page.tsx +++ b/apps/masterbots.ai/app/(browse)/page.tsx @@ -1,6 +1,6 @@ import ThreadList from '@/components/shared/thread-list' -import { BrowseCategoryTabs } from '@/components/routes/browse/browse-category-tabs' -import { BrowseSearchInput } from '@/components/routes/browse/browse-search-input' +import { CategoryTabs } from '@/components/shared/category-tabs/category-tabs' +import { BrowseInput } from '@/components/shared/browse-input' import { getBrowseThreads, getCategories } from '@/services/hasura' export default async function BrowsePage() { @@ -10,8 +10,8 @@ export default async function BrowsePage() { }) return (
- - + +
) diff --git a/apps/masterbots.ai/app/b/[id]/page.tsx b/apps/masterbots.ai/app/b/[id]/page.tsx index 1bbc32ec..e9fc5ccd 100644 --- a/apps/masterbots.ai/app/b/[id]/page.tsx +++ b/apps/masterbots.ai/app/b/[id]/page.tsx @@ -2,8 +2,8 @@ import { getChatbot, getBrowseThreads, getCategories } from '@/services/hasura' import { botNames } from '@/lib/bots-names' import ThreadList from '@/components/shared/thread-list' import AccountDetails from '@/components/shared/account-details' -import { BrowseCategoryTabs } from '@/components/routes/browse/browse-category-tabs' -import { BrowseSearchInput } from '@/components/routes/browse/browse-search-input' +import { CategoryTabs } from '@/components/shared/category-tabs/category-tabs' +import { BrowseInput } from '@/components/shared/browse-input' export default async function BotThreadsPage({ params @@ -27,8 +27,8 @@ export default async function BotThreadsPage({ return (
- - + + - - + + diff --git a/apps/masterbots.ai/components/routes/browse/browse-category-link.tsx b/apps/masterbots.ai/components/shared/category-tabs/category-link.tsx similarity index 97% rename from apps/masterbots.ai/components/routes/browse/browse-category-link.tsx rename to apps/masterbots.ai/components/shared/category-tabs/category-link.tsx index 21b3c0f3..f5969b4a 100644 --- a/apps/masterbots.ai/components/routes/browse/browse-category-link.tsx +++ b/apps/masterbots.ai/components/shared/category-tabs/category-link.tsx @@ -2,7 +2,7 @@ import { motion } from 'framer-motion' import type { Category } from '@repo/mb-genql' import Link from 'next/link' -export function BrowseCategoryLink({ +export function CategoryLink({ category, activeTab, onClick, diff --git a/apps/masterbots.ai/components/routes/browse/browse-category-tabs.tsx b/apps/masterbots.ai/components/shared/category-tabs/category-tabs.tsx similarity index 91% rename from apps/masterbots.ai/components/routes/browse/browse-category-tabs.tsx rename to apps/masterbots.ai/components/shared/category-tabs/category-tabs.tsx index 348c1d4b..0c7c8d6f 100644 --- a/apps/masterbots.ai/components/routes/browse/browse-category-tabs.tsx +++ b/apps/masterbots.ai/components/shared/category-tabs/category-tabs.tsx @@ -3,9 +3,9 @@ import type { Category } from '@repo/mb-genql' import { useEffect } from 'react' import { useBrowse } from '@/hooks/use-browse' -import { BrowseCategoryLink } from './browse-category-link' +import { CategoryLink } from './category-link' -export function BrowseCategoryTabs({ +export function CategoryTabs({ categories, initialCategory = 'all' }: { @@ -46,7 +46,7 @@ export function BrowseCategoryTabs({ return (
- {categories.map((category, key) => ( - Date: Mon, 8 Apr 2024 10:24:11 -0600 Subject: [PATCH 09/10] feat: copy shortlink --- .../(browse)/[category]/[threadId]/page.tsx | 2 - .../components/shared/copy-shortlink.tsx | 28 ++++++++ .../components/shared/shortlink-button.tsx | 71 ------------------- .../components/shared/thread-accordion.tsx | 19 ++--- .../components/shared/thread-heading.tsx | 48 +++++++------ 5 files changed, 66 insertions(+), 102 deletions(-) create mode 100644 apps/masterbots.ai/components/shared/copy-shortlink.tsx delete mode 100644 apps/masterbots.ai/components/shared/shortlink-button.tsx diff --git a/apps/masterbots.ai/app/(browse)/[category]/[threadId]/page.tsx b/apps/masterbots.ai/app/(browse)/[category]/[threadId]/page.tsx index bd44bb9b..b3b331cb 100644 --- a/apps/masterbots.ai/app/(browse)/[category]/[threadId]/page.tsx +++ b/apps/masterbots.ai/app/(browse)/[category]/[threadId]/page.tsx @@ -1,7 +1,6 @@ import { getCategories, getMessagePairs, getThread } from '@/services/hasura' import type { ChatPageProps } from '@/app/c/[chatbot]/[threadId]/page' -import Shortlink from '@/components/shared/shortlink-button' import { ThreadAccordion } from '@/components/shared/thread-accordion' import { CategoryTabs } from '@/components/shared/category-tabs/category-tabs' import { BrowseInput } from '@/components/shared/browse-input' @@ -17,7 +16,6 @@ export default async function ThreadLandingPage({ params }: ChatPageProps) {
- { + // Stop propagation to prevent form submission when clicking on the icon + e.preventDefault() + e.stopPropagation() + const formData = new FormData() + formData.set('url', url) + const { shortLink } = await shorten({}, formData) + navigator.clipboard + .writeText(shortLink) + .then(() => console.log('Shortlink copied to clipboard')) + .catch(error => + console.error('Error copying shortlink to clipboard: ', error) + ) + } + + return +} diff --git a/apps/masterbots.ai/components/shared/shortlink-button.tsx b/apps/masterbots.ai/components/shared/shortlink-button.tsx deleted file mode 100644 index 85c5bde8..00000000 --- a/apps/masterbots.ai/components/shared/shortlink-button.tsx +++ /dev/null @@ -1,71 +0,0 @@ -'use client' - -import Link from 'next/link' -import { useFormState, useFormStatus } from 'react-dom' -import { CardContent } from '@/components/ui/card' -import { Label } from '@/components/ui/label' -import { Input } from '@/components/ui/input' -import { Button } from '@/components/ui/button' -import { shorten } from '@/app/actions' - -const initialState = { - shortLink: '' -} - -export default function Shortlink() { - const [state, formAction] = useFormState(shorten, initialState) - - const url = window.location.href.replace( - 'http://localhost:3000', - 'https://dev.masterbots.ai' - ) - - return ( - - - - - - {state.shortLink ? ( -
-

- - {state.shortLink.replace(/^https?:\/\//, '')} - -

-
- ) : null} -
- ) -} - -function SubmitButton() { - const { pending } = useFormStatus() - - return ( - - ) -} - -function LoadingCircle() { - return ( - - ) -} diff --git a/apps/masterbots.ai/components/shared/thread-accordion.tsx b/apps/masterbots.ai/components/shared/thread-accordion.tsx index b3488f37..74d6f0ba 100644 --- a/apps/masterbots.ai/components/shared/thread-accordion.tsx +++ b/apps/masterbots.ai/components/shared/thread-accordion.tsx @@ -61,22 +61,25 @@ export function ThreadAccordion({ {key ? ( - p.userMessage.content +
{p.userMessage.content}
) : ( )}
- {p.chatGptMessage.map((message, index) => ( - - ))} +
+ {p.chatGptMessage.map((message, index) => ( + + ))} +
) diff --git a/apps/masterbots.ai/components/shared/thread-heading.tsx b/apps/masterbots.ai/components/shared/thread-heading.tsx index d061ffc9..604a3bc0 100644 --- a/apps/masterbots.ai/components/shared/thread-heading.tsx +++ b/apps/masterbots.ai/components/shared/thread-heading.tsx @@ -2,48 +2,53 @@ import { Thread } from '@repo/mb-genql' import { ShortMessage } from './thread-short-message' import { AccountAvatar } from './account-avatar' import { cn } from '@/lib/utils' +import Shortlink from './copy-shortlink' export function ThreadHeading({ thread, response, - question + question, + copy = false }: ThreadHeadingProps) { return (
- +
+ -
- {question} +
+ {question} - by + by - + +
+ {copy ? : null}
{response ? ( -
+
) : null} @@ -55,4 +60,5 @@ interface ThreadHeadingProps { thread: Thread response?: string question: string + copy?: boolean } From e15ef3edb50630319b83374c56d896ca02812c32 Mon Sep 17 00:00:00 2001 From: Gabo Esquivel Date: Mon, 8 Apr 2024 10:47:41 -0600 Subject: [PATCH 10/10] chore: disable getUserProfile --- apps/masterbots.ai/app/layout.tsx | 11 +++++- .../services/supabase/supa-server.service.ts | 37 ++++++++++--------- 2 files changed, 29 insertions(+), 19 deletions(-) diff --git a/apps/masterbots.ai/app/layout.tsx b/apps/masterbots.ai/app/layout.tsx index e3441711..6040683b 100644 --- a/apps/masterbots.ai/app/layout.tsx +++ b/apps/masterbots.ai/app/layout.tsx @@ -9,9 +9,18 @@ import { Providers } from '@/components/layout/providers' import { cn } from '@/lib/utils' import { GlobalStoreProvider } from '@/hooks/use-global-store' -export default async function RootLayout({ children }: RootLayoutProps) { +async function getCookieData(): Promise<{ hasuraJwt; userProfile }> { const hasuraJwt = cookies().get('hasuraJwt')?.value || '' const userProfile = cookies().get('userProfile')?.value || null + return new Promise(resolve => + setTimeout(() => { + resolve({ hasuraJwt, userProfile }) + }, 1000) + ) +} + +export default async function RootLayout({ children }: RootLayoutProps) { + const { hasuraJwt, userProfile } = await getCookieData() return ( diff --git a/apps/masterbots.ai/services/supabase/supa-server.service.ts b/apps/masterbots.ai/services/supabase/supa-server.service.ts index 4c61d863..b154df97 100644 --- a/apps/masterbots.ai/services/supabase/supa-server.service.ts +++ b/apps/masterbots.ai/services/supabase/supa-server.service.ts @@ -4,26 +4,27 @@ import { createSupabaseServerClient } from './supa-server-client' export async function getUserProfile(): Promise { try { - const supabase = await createSupabaseServerClient() - const { - data: { user } - } = await supabase.auth.getUser() - if (!user || !user.email) throw new Error('user not found') + return null + // const supabase = await createSupabaseServerClient() + // const { + // data: { user } + // } = await supabase.auth.getUser() + // if (!user || !user.email) throw new Error('user not found') - // TODO: use supabase - const userProfile = await getUser({ - email: user.email, - adminSecret: process.env.HASURA_GRAPHQL_ADMIN_SECRET || '' - }) + // // TODO: use supabase + // const userProfile = await getUser({ + // email: user.email, + // adminSecret: process.env.HASURA_GRAPHQL_ADMIN_SECRET || '' + // }) - if (!userProfile) throw new Error('user not found') - return { - userId: userProfile.userId, - username: userProfile.username, - name: '', - email: userProfile.email, - image: userProfile.profilePicture || '' - } + // if (!userProfile) throw new Error('user not found') + // return { + // userId: userProfile.userId, + // username: userProfile.username, + // name: '', + // email: userProfile.email, + // image: userProfile.profilePicture || '' + // } } catch (error) { console.log('GET USER PROFILE ERROR', error) return null