diff --git a/.github/workflows/sync-orama.yml b/.github/workflows/sync-orama.yml index 7de100df4eee6..2179e77f437e1 100644 --- a/.github/workflows/sync-orama.yml +++ b/.github/workflows/sync-orama.yml @@ -43,5 +43,6 @@ jobs: run: node --run sync-orama env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - ORAMA_INDEX_ID: ${{ github.event_name == 'push' && secrets.ORAMA_PRODUCTION_INDEX_ID || secrets.ORAMA_INDEX_ID }} - ORAMA_SECRET_KEY: ${{ github.event_name == 'push' && secrets.ORAMA_PRODUCTION_SECRET_KEY || secrets.ORAMA_SECRET_KEY }} + NEW_ORAMA_PROJECT_ID: ${{ github.event_name == 'push' && secrets.NEW_ORAMA_PRODUCTION_PROJECT_ID || secrets.NEW_ORAMA_PROJECT_ID }} + NEW_ORAMA_PRIVATE_API_KEY: ${{ github.event_name == 'push' && secrets.NEW_ORAMA_PRODUCTION_PRIVATE_API_KEY || secrets.NEW_ORAMA_PRIVATE_API_KEY }} + NEW_ORAMA_DATASOURCE_ID: ${{ github.event_name == 'push' && secrets.NEW_ORAMA_PRODUCTION_DATASOURCE_ID || secrets.NEW_ORAMA_DATASOURCE_ID }} diff --git a/.gitignore b/.gitignore index 6dbefc3ed5566..3e5db9342a1f8 100644 --- a/.gitignore +++ b/.gitignore @@ -48,4 +48,7 @@ test-results playwright-report ## MacOS Ignored Files -.DS_Store \ No newline at end of file +.DS_Store + +## Other Files +.env diff --git a/apps/site/components/Common/Search/index.tsx b/apps/site/components/Common/Search/index.tsx deleted file mode 100644 index 0c12664b61826..0000000000000 --- a/apps/site/components/Common/Search/index.tsx +++ /dev/null @@ -1,145 +0,0 @@ -'use client'; - -import { OramaSearchBox, OramaSearchButton } from '@orama/react-components'; -import { useTranslations, useLocale } from 'next-intl'; -import { useTheme } from 'next-themes'; -import type { FC } from 'react'; - -import { useRouter } from '#site/navigation.mjs'; -import { - ORAMA_CLOUD_ENDPOINT, - ORAMA_CLOUD_API_KEY, - DEFAULT_ORAMA_QUERY_PARAMS, - DEFAULT_ORAMA_SUGGESTIONS, - BASE_URL, -} from '#site/next.constants.mjs'; - -type ResultMapDescription = { - path: string; - pageSectionTitle: string; -}; - -type ResultMapPath = { path: string; siteSection: string }; - -import { themeConfig, translationKeys } from './utils'; - -const uppercaseFirst = (word: string) => - word.charAt(0).toUpperCase() + word.slice(1); - -const getFormattedPath = (path: string, title: string) => - `${path - .replace(/#.+$/, '') - .split('/') - .map(element => element.replaceAll('-', ' ')) - .map(element => uppercaseFirst(element)) - .filter(Boolean) - .join(' > ')} — ${title}`; - -const SearchButton: FC = () => { - const { resolvedTheme } = useTheme(); - const t = useTranslations(); - const locale = useLocale(); - const colorScheme = resolvedTheme as 'light' | 'dark'; - const router = useRouter(); - - const sourceMap = { - title: 'pageSectionTitle', - description: 'formattedPath', - path: 'path', - }; - - const resultMap = { - ...sourceMap, - description: ({ path, pageSectionTitle }: ResultMapDescription) => - getFormattedPath(path, pageSectionTitle), - path: ({ path, siteSection }: ResultMapPath) => - siteSection.toLowerCase() === 'docs' ? `/${path}` : `/${locale}/${path}`, - section: 'siteSection', - }; - - return ( - <> - - {t('components.search.searchPlaceholder')} - - - [key, t(`components.search.${key}`)]) - )} - searchParams={DEFAULT_ORAMA_QUERY_PARAMS} - suggestions={DEFAULT_ORAMA_SUGGESTIONS} - chatMarkdownLinkHref={({ href }) => { - if (!href) { - return href; - } - - const baseURLObject = new URL(BASE_URL); - const baseURLHostName = baseURLObject.hostname; - - const searchBoxURLObject = new URL(href); - const searchBoxURLHostName = searchBoxURLObject.hostname; - const serachBoxURLPathName = searchBoxURLObject.pathname; - - // We do not want to add the locale to the url for external links and docs links - if ( - baseURLHostName !== searchBoxURLHostName || - serachBoxURLPathName.startsWith('/docs/') - ) { - return href; - } - - const URLWithLocale = new URL( - `${locale}${searchBoxURLObject.pathname}`, - searchBoxURLObject.origin - ); - - return URLWithLocale.href; - }} - onAnswerSourceClick={event => { - event.preventDefault(); - - const baseURLObject = new URL(BASE_URL); - - const { path } = event.detail.source; - - const finalPath = path.startsWith('docs/') - ? path - : `${locale}/${path}`; - - const finalURL = new URL(finalPath, baseURLObject); - - window.open(finalURL, '_blank'); - }} - onSearchResultClick={event => { - event.preventDefault(); - - const fullURLObject = new URL(event.detail.result.path, BASE_URL); - - // result.path already contains LOCALE. Locale is set to undefined here so router does not add it once again. - router.push(fullURLObject.href, { locale: undefined }); - }} - /> - - ); -}; - -export default SearchButton; diff --git a/apps/site/components/Common/Search/utils.ts b/apps/site/components/Common/Search/utils.ts deleted file mode 100644 index dca1281ad7154..0000000000000 --- a/apps/site/components/Common/Search/utils.ts +++ /dev/null @@ -1,60 +0,0 @@ -export const themeConfig = { - typography: { - '--font-primary': 'var(--font-open-sans)', - }, - colors: { - light: { - '--text-color-primary': 'var(--color-neutral-900)', - '--text-color-accent': 'var(--color-green-600)', - '--background-color-secondary': 'var(--color-neutral-100)', - '--background-color-tertiary': 'var(--color-neutral-300)', - '--border-color-accent': 'var(--color-green-600)', - '--border-color-primary': 'var(--color-neutral-200)', - '--border-color-tertiary': 'var(--color-green-700)', - '--button-background-color-primary': 'var(--color-green-600)', - '--button-background-color-secondary': 'var(--color-white)', - '--button-background-color-secondary-hover': 'var(--color-neutral-100)', - '--button-border-color-secondary': 'var(--color-neutral-300)', - '--button-text-color-secondary': 'var(--color-neutral-900)', - '--chat-button-border-color-gradientThree': 'var(--color-green-400)', - '--chat-button-border-color-gradientFour': 'var(--color-green-700)', - '--chat-button-background-color-gradientOne': 'var(--color-green-600)', - '--chat-button-background-color-gradientTwo': 'var(--color-green-300)', - }, - dark: { - '--text-color-primary': 'var(--color-neutral-100)', - '--text-color-accent': 'var(--color-green-400)', - '--background-color-secondary': 'var(--color-neutral-950)', - '--background-color-tertiary': 'var(--color-neutral-900)', - '--border-color-accent': 'var(--color-green-400)', - '--border-color-primary': 'var(--color-neutral-900)', - '--border-color-tertiary': 'var(--color-green-300)', - '--button-background-color-primary': 'var(--color-green-400)', - '--button-background-color-secondary': 'var(--color-neutral-950)', - '--button-background-color-secondary-hover': 'var(--color-neutral-900)', - '--button-border-color-secondary': 'var(--color-neutral-900)', - '--button-text-color-secondary': 'var(--color-neutral-200)', - '--chat-button-border-color-gradientThree': 'var(--color-green-400)', - '--chat-button-border-color-gradientFour': 'var(--color-green-700)', - '--chat-button-background-color-gradientOne': 'var(--color-green-400)', - '--chat-button-background-color-gradientTwo': 'var(--color-green-800)', - }, - }, -}; - -export const translationKeys = [ - 'searchPlaceholder', - 'chatPlaceholder', - 'noResultsFoundFor', - 'suggestions', - 'seeAll', - 'addMore', - 'clearChat', - 'errorMessage', - 'disclaimer', - 'startYourSearch', - 'initErrorSearch', - 'initErrorChat', - 'chatButtonLabel', - 'searchButtonLabel', -] as const; diff --git a/apps/site/components/Common/Searchbox/ChatActions/index.module.css b/apps/site/components/Common/Searchbox/ChatActions/index.module.css new file mode 100644 index 0000000000000..a6081326ce8f9 --- /dev/null +++ b/apps/site/components/Common/Searchbox/ChatActions/index.module.css @@ -0,0 +1,44 @@ +@reference "../../../../styles/index.css"; + +.chatActionsContainer { + @apply flex + items-center + justify-end; +} + +.chatActionsList { + @apply flex + list-none + items-center + gap-2 + p-0; +} + +.chatAction { + @apply cursor-pointer + rounded-full + p-2 + text-neutral-800 + duration-300 + hover:bg-neutral-300 + focus:bg-neutral-300 + focus:outline-none + motion-safe:transition-colors + dark:text-neutral-400 + dark:hover:bg-neutral-900 + dark:focus:bg-neutral-900; + + svg { + @apply size-4; + } +} + +.chatActionIconSelected { + @apply text-green-600 + dark:text-green-400; +} + +.chatActionDisaliked { + @apply text-neutral-900 + dark:text-neutral-800; +} diff --git a/apps/site/components/Common/Searchbox/ChatActions/index.tsx b/apps/site/components/Common/Searchbox/ChatActions/index.tsx new file mode 100644 index 0000000000000..79f25bfd0e840 --- /dev/null +++ b/apps/site/components/Common/Searchbox/ChatActions/index.tsx @@ -0,0 +1,70 @@ +'use client'; + +import { + DocumentCheckIcon, + ClipboardIcon, + ArrowPathIcon, + HandThumbDownIcon, +} from '@heroicons/react/24/solid'; +import type { Interaction } from '@orama/core'; +import { ChatInteractions } from '@orama/ui/components'; +import classNames from 'classnames'; +import type { FC } from 'react'; +import { useState } from 'react'; + +import styles from './index.module.css'; + +type ChatActionsProps = { + interaction: Interaction; +}; + +export const ChatActions: FC = ({ interaction }) => { + const [isDisliked, setIsDisliked] = useState(false); + + const dislikeMessage = () => setIsDisliked(!isDisliked); + + if (!interaction.response) { + return null; + } + + return ( +
+
    +
  • + + + +
  • +
  • + + {(copied: boolean) => + copied ? ( + + ) : ( + + ) + } + +
  • + {!interaction.loading && ( +
  • + +
  • + )} +
+
+ ); +}; diff --git a/apps/site/components/Common/Searchbox/ChatInput/index.module.css b/apps/site/components/Common/Searchbox/ChatInput/index.module.css new file mode 100644 index 0000000000000..3cb7979d6f233 --- /dev/null +++ b/apps/site/components/Common/Searchbox/ChatInput/index.module.css @@ -0,0 +1,99 @@ +@reference "../../../../styles/index.css"; + +.textareaContainer { + @apply px-1; +} + +.textareaWrapper { + @apply flex + items-center + rounded-2xl + border + border-neutral-300 + bg-neutral-100 + py-2 + pl-3 + pr-1 + dark:border-neutral-900 + dark:bg-neutral-950; +} + +.textareaField { + @apply flex-1 + border-0 + bg-transparent + text-neutral-900 + focus:outline-none + dark:text-neutral-200; +} + +.textareaButton { + @apply cursor-pointer + rounded-xl + bg-green-600 + p-2 + text-white + duration-300 + focus:bg-green-600/75 + focus:outline-none + disabled:cursor-not-allowed + disabled:bg-neutral-200/60 + disabled:text-neutral-800 + motion-safe:transition-colors + dark:bg-green-400 + dark:text-neutral-400 + focus:dark:bg-green-400/75 + disabled:dark:bg-neutral-900/60; + + svg { + @apply size-4; + } +} + +.textareaFooter { + @apply pt-1 + text-center + text-xs + text-neutral-800 + sm:text-sm + dark:text-neutral-500; +} + +.suggestionsWrapper { + @apply mb-4 + flex + items-center + gap-2 + overflow-x-auto + px-1 + text-sm + lg:justify-center; + + &::-webkit-scrollbar { + @apply hidden; + } +} + +.suggestionsItem { + @apply flex + size-max + cursor-pointer + whitespace-nowrap + rounded-full + border + border-neutral-300 + bg-neutral-200 + px-3 + py-1 + text-neutral-900 + duration-300 + hover:bg-neutral-300 + focus:bg-neutral-300 + focus:outline-none + motion-safe:transition-colors + dark:border-neutral-900 + dark:bg-neutral-950 + dark:text-neutral-200 + dark:hover:bg-neutral-900 + dark:focus:bg-neutral-900; +} diff --git a/apps/site/components/Common/Searchbox/ChatInput/index.tsx b/apps/site/components/Common/Searchbox/ChatInput/index.tsx new file mode 100644 index 0000000000000..013d86518b611 --- /dev/null +++ b/apps/site/components/Common/Searchbox/ChatInput/index.tsx @@ -0,0 +1,77 @@ +'use client'; + +import { PaperAirplaneIcon } from '@heroicons/react/20/solid'; +import { PauseCircleIcon } from '@heroicons/react/24/solid'; +import { PromptTextArea, Suggestions } from '@orama/ui/components'; +import { useChat } from '@orama/ui/hooks'; +import { useTranslations } from 'next-intl'; +import type { FC } from 'react'; +import { useEffect, useRef } from 'react'; + +import styles from './index.module.css'; + +export const ChatInput: FC = () => { + const t = useTranslations(); + const { + context: { interactions }, + } = useChat(); + const textareaRef = useRef(null); + + const suggestions = [ + t('components.search.suggestionOne'), + t('components.search.suggestionTwo'), + t('components.search.suggestionThree'), + ]; + + const hasInteractions = !!interactions?.length; + + useEffect(() => { + const timeoutId = setTimeout(() => { + textareaRef.current?.focus(); + }, 100); + + return () => { + clearTimeout(timeoutId); + }; + }, []); + + return ( + <> + {!hasInteractions && ( + + {suggestions.map(suggestion => ( + + {suggestion} + + ))} + + )} +
+ + + } + className={styles.textareaButton} + > + + + +
+ {t('components.search.disclaimer')} +
+
+ + ); +}; diff --git a/apps/site/components/Common/Searchbox/ChatInteractions/index.module.css b/apps/site/components/Common/Searchbox/ChatInteractions/index.module.css new file mode 100644 index 0000000000000..2bb4384ee51f5 --- /dev/null +++ b/apps/site/components/Common/Searchbox/ChatInteractions/index.module.css @@ -0,0 +1,64 @@ +@reference "../../../../styles/index.css"; + +.chatInteractionsContainer { + @apply relative + mb-6 + flex + h-full + flex-1 + flex-col + items-start + overflow-auto + px-1; + + &::-webkit-scrollbar { + @apply size-1.5; + } + + &::-webkit-scrollbar-track { + @apply rounded-md + bg-transparent; + } + + &::-webkit-scrollbar-thumb { + @apply rounded-md + bg-neutral-900; + } +} + +.chatInteractionsWrapper { + @apply flex + w-full + flex-wrap + gap-6; + + > div { + @apply w-full; + } +} + +.scrollDownButton { + @apply absolute + bottom-36 + left-1/2 + inline-flex + -translate-x-1/2 + items-center + justify-center + rounded-xl + bg-neutral-200 + p-2 + text-neutral-900 + duration-300 + focus:bg-neutral-300 + focus:outline-none + motion-safe:transition-colors + lg:bottom-28 + dark:bg-neutral-900 + dark:text-neutral-200 + focus:dark:bg-neutral-800; + + svg { + @apply size-4; + } +} diff --git a/apps/site/components/Common/Searchbox/ChatInteractions/index.tsx b/apps/site/components/Common/Searchbox/ChatInteractions/index.tsx new file mode 100644 index 0000000000000..2fd2a1c77f026 --- /dev/null +++ b/apps/site/components/Common/Searchbox/ChatInteractions/index.tsx @@ -0,0 +1,46 @@ +'use client'; + +import { ArrowDownIcon } from '@heroicons/react/24/solid'; +import type { Interaction } from '@orama/core'; +import { ChatInteractions } from '@orama/ui/components'; +import { useScrollableContainer } from '@orama/ui/hooks/useScrollableContainer'; +import { useTranslations } from 'next-intl'; + +import { ChatMessage } from '../ChatMessage'; +import styles from './index.module.css'; + +export const ChatInteractionsContainer = () => { + const t = useTranslations(); + const { + containerRef, + scrollToBottom, + recalculateGoToBottomButton, + showGoToBottomButton, + } = useScrollableContainer(); + + return ( + <> +
+ scrollToBottom({ animated: true })} + className={styles.chatInteractionsWrapper} + > + {(interaction: Interaction) => ( + + )} + +
+ {showGoToBottomButton && ( + + )} + + ); +}; diff --git a/apps/site/components/Common/Searchbox/ChatMessage/index.module.css b/apps/site/components/Common/Searchbox/ChatMessage/index.module.css new file mode 100644 index 0000000000000..792c1857f9b8c --- /dev/null +++ b/apps/site/components/Common/Searchbox/ChatMessage/index.module.css @@ -0,0 +1,50 @@ +@reference "../../../../styles/index.css"; + +.chatUserPrompt { + @apply py-3; + + p { + @apply max-w-2xl + rounded-xl + text-neutral-900 + dark:text-neutral-200; + } +} + +.chatAssistantMessageWrapper { + @apply my-2 + rounded-xl + bg-neutral-100 + px-4 + py-1 + text-neutral-900 + empty:hidden + dark:bg-neutral-950 + dark:text-neutral-200; +} + +.typingIndicator { + @apply flex + items-center + gap-1 + rounded-xl + bg-neutral-200 + p-4 + dark:bg-neutral-950; +} + +.typingDot { + @apply animate-dot-move + size-1 + rounded-full + bg-neutral-500 + dark:bg-neutral-400; + + &:nth-child(2) { + @apply animate-dot-move-delay-200; + } + + &:nth-child(3) { + @apply animate-dot-move-delay-400; + } +} diff --git a/apps/site/components/Common/Searchbox/ChatMessage/index.tsx b/apps/site/components/Common/Searchbox/ChatMessage/index.tsx new file mode 100644 index 0000000000000..8b94f31ef9f96 --- /dev/null +++ b/apps/site/components/Common/Searchbox/ChatMessage/index.tsx @@ -0,0 +1,71 @@ +import type { Interaction } from '@orama/core'; +import { ChatInteractions } from '@orama/ui/components'; +import type { FC } from 'react'; + +import { ChatActions } from '../ChatActions'; +import ChatSources from '../ChatSources'; +import styles from './index.module.css'; + +type ChatMessageProps = { + interaction: Interaction; +}; + +const TypingIndicator: FC = () => ( +
+ + + +
+); + +export const ChatMessage: FC = ({ interaction }) => { + if (!interaction) { + return null; + } + + return ( + <> + +

{interaction?.query}

+
+ + + + +
+ +
+
+ + {interaction.response && ( +
+ + {interaction.response || ''} + + +
+ )} + + ); +}; diff --git a/apps/site/components/Common/Searchbox/ChatSources/index.module.css b/apps/site/components/Common/Searchbox/ChatSources/index.module.css new file mode 100644 index 0000000000000..0f6f09a15e77b --- /dev/null +++ b/apps/site/components/Common/Searchbox/ChatSources/index.module.css @@ -0,0 +1,53 @@ +@reference "../../../../styles/index.css"; + +.chatSources { + @apply mb-4 + flex + flex-nowrap + items-center + gap-3 + overflow-x-scroll + scroll-smooth + [-ms-overflow-style:none] + [scrollbar-width:none]; + + &::-webkit-scrollbar { + @apply hidden; + } +} + +.chatSource { + @apply flex + max-w-full + items-center + gap-2 + text-base; +} + +.chatSourceLink { + @apply w-3xs + rounded-xl + bg-neutral-100 + px-4 + py-2 + text-neutral-900 + duration-300 + hover:bg-neutral-200 + focus:bg-neutral-200 + focus:outline-none + motion-safe:transition-colors + dark:bg-neutral-950 + dark:text-neutral-200 + hover:dark:bg-neutral-900 + focus:dark:bg-neutral-900; +} + +.chatSourceTitle { + @apply max-w-full + overflow-hidden + truncate + text-ellipsis + whitespace-nowrap + text-sm + font-semibold; +} diff --git a/apps/site/components/Common/Searchbox/ChatSources/index.tsx b/apps/site/components/Common/Searchbox/ChatSources/index.tsx new file mode 100644 index 0000000000000..22cde20ffd8e5 --- /dev/null +++ b/apps/site/components/Common/Searchbox/ChatSources/index.tsx @@ -0,0 +1,47 @@ +import type { Interaction, AnyObject } from '@orama/core'; +import { ChatInteractions } from '@orama/ui/components'; +import type { FC } from 'react'; + +import styles from './index.module.css'; +import type { Document } from '../DocumentLink'; +import { DocumentLink } from '../DocumentLink'; + +type ChatSourcesProps = { + interaction: Interaction; +}; + +const ChatSources: FC = ({ interaction }) => { + if (!interaction?.sources) { + return null; + } + + return ( + + {(document: AnyObject, index: number) => ( +
+ {!!document.pageSectionTitle && + typeof document.pageSectionTitle === 'string' && ( + + + {document.pageSectionTitle && + document.pageSectionTitle.length > 25 + ? `${document.pageSectionTitle.substring(0, 25)}...` + : document.pageSectionTitle} + + + )} +
+ )} +
+ ); +}; + +export default ChatSources; diff --git a/apps/site/components/Common/Searchbox/DocumentLink/index.module.css b/apps/site/components/Common/Searchbox/DocumentLink/index.module.css new file mode 100644 index 0000000000000..31b802682b825 --- /dev/null +++ b/apps/site/components/Common/Searchbox/DocumentLink/index.module.css @@ -0,0 +1,32 @@ +@reference "../../../../styles/index.css"; + +.documentLink { + @apply rounded-xl + bg-white + px-4 + py-2 + text-neutral-900 + duration-300 + hover:bg-neutral-200 + focus:bg-neutral-200 + motion-safe:transition-colors + lg:bg-neutral-100 + dark:bg-neutral-950 + dark:text-neutral-200 + hover:dark:bg-neutral-900 + focus:dark:bg-neutral-900; + + svg { + @apply size-5; + } +} + +.documentTitle { + @apply max-w-full + overflow-hidden + truncate + text-ellipsis + whitespace-nowrap + text-sm + font-semibold; +} diff --git a/apps/site/components/Common/Searchbox/DocumentLink/index.tsx b/apps/site/components/Common/Searchbox/DocumentLink/index.tsx new file mode 100644 index 0000000000000..44c507ac4f13d --- /dev/null +++ b/apps/site/components/Common/Searchbox/DocumentLink/index.tsx @@ -0,0 +1,50 @@ +'use client'; + +import Link from 'next/link'; +import { useLocale } from 'next-intl'; +import type { FC } from 'react'; + +import styles from './index.module.css'; + +export type Document = { + path: string; + siteSection: string; + pageSectionTitle?: string; +}; + +type DocumentLinkProps = { + document: Document; + className?: string; + children?: React.ReactNode; + 'data-focus-on-arrow-nav'?: boolean; +} & React.AnchorHTMLAttributes; + +export const DocumentLink: FC = ({ + document, + className = styles.documentLink, + children, + 'data-focus-on-arrow-nav': dataFocusOnArrowNav, + ...props +}) => { + const locale = useLocale(); + + const href = + document.siteSection?.toLowerCase() === 'docs' + ? `/${document.path}` + : `/${locale}/${document.path}`; + + return ( + + {children || ( + + {document.pageSectionTitle} + + )} + + ); +}; diff --git a/apps/site/components/Common/Searchbox/EmptyResults/index.module.css b/apps/site/components/Common/Searchbox/EmptyResults/index.module.css new file mode 100644 index 0000000000000..c77b6ccf6c4b3 --- /dev/null +++ b/apps/site/components/Common/Searchbox/EmptyResults/index.module.css @@ -0,0 +1,59 @@ +@reference "../../../../styles/index.css"; + +.suggestionsWrapper { + @apply flex + min-h-0 + flex-1 + flex-col + overflow-y-auto + pb-4 + text-neutral-900 + dark:text-neutral-200; +} + +.suggestionsList { + @apply mt-1 + space-y-1; +} + +.suggestionsTitle { + @apply my-3 + text-xs + font-semibold + uppercase + text-neutral-800 + dark:text-neutral-500; +} + +.suggestionItem { + @apply flex + cursor-pointer + items-center + gap-2 + rounded-lg + border + border-transparent + py-2 + text-sm + text-green-600 + focus-visible:border-green-600 + focus-visible:outline-none + dark:text-green-400 + dark:focus-visible:border-green-400; + + svg { + @apply size-5; + } +} + +.noResultsWrapper { + @apply pb-31 + flex + h-full + items-center + justify-center + pt-10 + text-sm + text-neutral-800 + dark:text-neutral-500; +} diff --git a/apps/site/components/Common/Searchbox/EmptyResults/index.tsx b/apps/site/components/Common/Searchbox/EmptyResults/index.tsx new file mode 100644 index 0000000000000..9671ed04ae32f --- /dev/null +++ b/apps/site/components/Common/Searchbox/EmptyResults/index.tsx @@ -0,0 +1,65 @@ +'use client'; + +import { SparklesIcon } from '@heroicons/react/24/outline'; +import { SearchResults, Suggestions } from '@orama/ui/components'; +import { useTranslations } from 'next-intl'; +import { type FC } from 'react'; + +import { useSearchbox } from '#site/providers/searchboxProvider'; + +import styles from './index.module.css'; + +export const EmptyResults: FC = () => { + const t = useTranslations(); + const searchbox = useSearchbox(); + const isSearchMode = searchbox?.mode === 'search'; + + return ( + + {term => ( + <> + {term ? ( +
+

+ {t('components.search.noResultsFoundFor')} "{term}" +

+
+ ) : ( + +

+ {t('components.search.suggestions')} +

+ searchbox?.switchTo('chat')} + tabIndex={isSearchMode ? 0 : -1} + aria-hidden={!isSearchMode} + className={styles.suggestionItem} + > + + {t('components.search.suggestionOne')} + + searchbox?.switchTo('chat')} + tabIndex={isSearchMode ? 0 : -1} + aria-hidden={!isSearchMode} + className={styles.suggestionItem} + > + + {t('components.search.suggestionTwo')} + + searchbox?.switchTo('chat')} + className={styles.suggestionItem} + > + + {t('components.search.suggestionThree')} + +
+ )} + + )} +
+ ); +}; diff --git a/apps/site/components/Common/Searchbox/Footer/index.module.css b/apps/site/components/Common/Searchbox/Footer/index.module.css new file mode 100644 index 0000000000000..95ec47b281a27 --- /dev/null +++ b/apps/site/components/Common/Searchbox/Footer/index.module.css @@ -0,0 +1,53 @@ +@reference "../../../../styles/index.css"; + +.footer { + @apply flex + justify-center + border-t + border-neutral-200 + bg-neutral-100 + p-4 + align-baseline + lg:justify-between + lg:rounded-b-xl + dark:border-neutral-900 + dark:bg-neutral-950; +} + +.poweredByLink { + @apply flex + items-center + gap-2 + text-sm + text-neutral-800 + dark:text-neutral-600; +} + +.shortcutWrapper { + @apply hidden + items-center + gap-2 + lg:flex; +} + +.shortcutItem { + @apply flex + items-center + gap-2 + text-xs + text-neutral-800 + dark:text-neutral-600; +} + +.shortcutKey { + @apply font-ibm-plex-mono + rounded-md + bg-neutral-200 + p-1 + text-xs + dark:bg-neutral-900; + + svg { + @apply size-4; + } +} diff --git a/apps/site/components/Common/Searchbox/Footer/index.tsx b/apps/site/components/Common/Searchbox/Footer/index.tsx new file mode 100644 index 0000000000000..70f74dc553898 --- /dev/null +++ b/apps/site/components/Common/Searchbox/Footer/index.tsx @@ -0,0 +1,67 @@ +'use client'; + +import { + ArrowTurnDownLeftIcon, + ArrowDownIcon, + ArrowUpIcon, +} from '@heroicons/react/24/solid'; +import Image from 'next/image'; +import { useTranslations } from 'next-intl'; +import { useTheme } from 'next-themes'; + +import styles from './index.module.css'; + +export const Footer = () => { + const t = useTranslations(); + const { resolvedTheme } = useTheme(); + + const oramaLogo = `https://website-assets.oramasearch.com/orama-when-${resolvedTheme}.svg`; + + return ( +
+
+
+ + + + + {t('components.search.keyboardShortcuts.select')} + +
+
+ + + + + + + + {t('components.search.keyboardShortcuts.navigate')} + +
+
+ esc + + {t('components.search.keyboardShortcuts.close')} + +
+
+ +
+ ); +}; diff --git a/apps/site/components/Common/Searchbox/InnerSearchboxModal/index.module.css b/apps/site/components/Common/Searchbox/InnerSearchboxModal/index.module.css new file mode 100644 index 0000000000000..211eedd672b8d --- /dev/null +++ b/apps/site/components/Common/Searchbox/InnerSearchboxModal/index.module.css @@ -0,0 +1,19 @@ +@reference "../../../../styles/index.css"; + +.mobileChatContainer { + @apply flex + grow + flex-col + overflow-hidden + px-4 + pb-4; +} + +.mobileChatTop { + @apply grow + overflow-hidden; +} + +.mobileChatBottom { + @apply mt-4; +} diff --git a/apps/site/components/Common/Searchbox/InnerSearchboxModal/index.tsx b/apps/site/components/Common/Searchbox/InnerSearchboxModal/index.tsx new file mode 100644 index 0000000000000..99b3385250a70 --- /dev/null +++ b/apps/site/components/Common/Searchbox/InnerSearchboxModal/index.tsx @@ -0,0 +1,68 @@ +'use client'; +import type { FC, PropsWithChildren } from 'react'; +import { useEffect, useState, useRef } from 'react'; + +import { useSearchbox } from '#site/providers/searchboxProvider'; + +import { ChatInput } from '../ChatInput'; +import { ChatInteractionsContainer } from '../ChatInteractions'; +import { Footer } from '../Footer'; +import { MobileTopBar } from '../MobileTopBar'; +import { Search } from '../Search'; +import { SlidingChatPanel } from '../SlidingChatPanel'; +import styles from './index.module.css'; + +export const InnerSearchboxModal: FC = () => { + const searchbox = useSearchbox(); + const [isMobileScreen, setIsMobileScreen] = useState(false); + const searchInputRef = useRef(null); + + const displaySearch = + !isMobileScreen || (isMobileScreen && searchbox?.mode === 'search'); + + useEffect(() => { + const checkScreenSize = () => { + setIsMobileScreen(window.innerWidth < 1024); + }; + checkScreenSize(); + window.addEventListener('resize', checkScreenSize); + return () => { + window.removeEventListener('resize', checkScreenSize); + }; + }, []); + + return ( + <> + {isMobileScreen && ( + + )} + {displaySearch && } + {isMobileScreen && searchbox?.mode === 'chat' && ( + <> +
+
+ +
+
+ +
+
+