From dfb4495e4de5b2a9b670ff2534e2e2d33ede7365 Mon Sep 17 00:00:00 2001 From: wwayne Date: Thu, 6 Jun 2024 16:40:58 +0800 Subject: [PATCH] update --- ee/tabby-ui/app/(home)/page.tsx | 37 ++++ ee/tabby-ui/app/globals.css | 4 + ee/tabby-ui/app/search/components/search.tsx | 214 +++++++------------ ee/tabby-ui/components/textarea-search.tsx | 81 +++++++ ee/tabby-ui/components/ui/icons.tsx | 11 +- ee/tabby-ui/lib/constants/index.ts | 3 +- 6 files changed, 215 insertions(+), 135 deletions(-) create mode 100644 ee/tabby-ui/components/textarea-search.tsx diff --git a/ee/tabby-ui/app/(home)/page.tsx b/ee/tabby-ui/app/(home)/page.tsx index 44bd48d79e94..1881aa6e4daa 100644 --- a/ee/tabby-ui/app/(home)/page.tsx +++ b/ee/tabby-ui/app/(home)/page.tsx @@ -1,10 +1,14 @@ 'use client' import { useState } from 'react' +import Image from 'next/image' import Link from 'next/link' import { useRouter } from 'next/navigation' +import logoUrl from '@/assets/tabby.png' import { noop } from 'lodash-es' +import { SESSION_STORAGE_KEY } from '@/lib/constants' +import { useEnableSearch } from '@/lib/experiment-flags' import { graphql } from '@/lib/gql/generates' import { useHealth } from '@/lib/hooks/use-health' import { useMe } from '@/lib/hooks/use-me' @@ -14,6 +18,7 @@ import { useSignOut } from '@/lib/tabby/auth' import { useMutation } from '@/lib/tabby/gql' import { Button } from '@/components/ui/button' import { CardContent, CardFooter } from '@/components/ui/card' +import { Dialog, DialogContent, DialogTrigger } from '@/components/ui/dialog' import { IconChat, IconCode, @@ -22,6 +27,7 @@ import { IconLogout, IconMail, IconRotate, + IconSearch, IconSpinner, IconUser, IconVSCode @@ -36,6 +42,7 @@ import { } from '@/components/ui/tooltip' import { CopyButton } from '@/components/copy-button' import SlackDialog from '@/components/slack-dialog' +import TextAreaSearch from '@/components/textarea-search' import { ThemeToggle } from '@/components/theme-toggle' import { UserAvatar } from '@/components/user-avatar' @@ -154,6 +161,7 @@ function IDELink({ } function MainPanel() { + const [searchFlag] = useEnableSearch() const { data: healthInfo } = useHealth() const [{ data }] = useMe() const isChatEnabled = useIsChatEnabled() @@ -168,6 +176,12 @@ function MainPanel() { await signOut() setSignOutLoading(false) } + + const onSearch = (question: string) => { + sessionStorage.setItem(SESSION_STORAGE_KEY.SEARCH_INITIAL_MSG, question) + window.open('/search') + } + return (
@@ -210,6 +224,29 @@ function MainPanel() { } target="_blank"> Code Browser + {searchFlag.value && isChatEnabled && ( + + +
+
+ +
+
+ Search +
+
+
+ +
+ logo +

+ The Private Search Assistant +

+ +
+
+
+ )} } onClick={handleSignOut}> Sign out {signOutLoading && } diff --git a/ee/tabby-ui/app/globals.css b/ee/tabby-ui/app/globals.css index a8bfbe91edde..bf209eb9a159 100644 --- a/ee/tabby-ui/app/globals.css +++ b/ee/tabby-ui/app/globals.css @@ -91,3 +91,7 @@ .prose-full-width { max-width: none !important; } + +.dialog-without-close-btn > button { + display: none; +} diff --git a/ee/tabby-ui/app/search/components/search.tsx b/ee/tabby-ui/app/search/components/search.tsx index 77f278f27cf0..b0d222a2c089 100644 --- a/ee/tabby-ui/app/search/components/search.tsx +++ b/ee/tabby-ui/app/search/components/search.tsx @@ -5,11 +5,12 @@ import Image from 'next/image' import logoUrl from '@/assets/tabby.png' import { Message } from 'ai' import { nanoid } from 'nanoid' -import TextareaAutosize from 'react-textarea-autosize' import remarkGfm from 'remark-gfm' import remarkMath from 'remark-math' +import { SESSION_STORAGE_KEY } from '@/lib/constants' import { useEnableSearch } from '@/lib/experiment-flags' +import { useIsChatEnabled } from '@/lib/hooks/use-server-info' import fetcher from '@/lib/tabby/fetcher' import { AnswerRequest } from '@/lib/types' import { cn } from '@/lib/utils' @@ -21,7 +22,6 @@ import { HoverCardTrigger } from '@/components/ui/hover-card' import { - IconArrowRight, IconBlocks, IconChevronRight, IconLayers, @@ -37,7 +37,6 @@ import { SheetContent, SheetDescription, SheetHeader, - SheetTitle, SheetTrigger } from '@/components/ui/sheet' import { Skeleton } from '@/components/ui/skeleton' @@ -46,6 +45,7 @@ import { ButtonScrollToBottom } from '@/components/button-scroll-to-bottom' import { useTabbyAnswer } from '@/components/chat/use-tabby-answer' import { CopyButton } from '@/components/copy-button' import { MemoizedReactMarkdown } from '@/components/markdown' +import TextAreaSearch from '@/components/textarea-search' import './search.css' @@ -88,6 +88,7 @@ const tabbyFetcher = ((url: string, init?: RequestInit) => { }) as typeof fetch export function Search() { + const isChatEnabled = useIsChatEnabled() const [searchFlag] = useEnableSearch() const [conversation, setConversation] = useState([]) @@ -101,6 +102,16 @@ export function Search() { fetcher: tabbyFetcher }) + useEffect(() => { + const initialQuestion = sessionStorage.getItem( + SESSION_STORAGE_KEY.SEARCH_INITIAL_MSG + ) + if (initialQuestion) { + onSubmitSearch(initialQuestion) + sessionStorage.removeItem(SESSION_STORAGE_KEY.SEARCH_INITIAL_MSG) + } + }, []) + useEffect(() => { if (title) return document.title = title @@ -211,7 +222,7 @@ export function Search() { triggerRequest(answerRequest) } - if (!searchFlag.value) { + if (!searchFlag.value || !isChatEnabled) { return <> } @@ -273,27 +284,22 @@ export function Search() { className={cn( 'fixed left-1/2 flex flex-col items-center transition-all md:-ml-[24rem] md:w-[48rem] md:p-6', { - 'bottom-2/3': noConversation, + 'bottom-1/2 -mt-48': noConversation, 'bottom-0 min-h-[6rem]': !noConversation } )} > {noConversation && ( <> - logo + logo

- Your private search engine (TODO) + The Private Search Assistant

)} {!isLoading && (
- +
)}
@@ -612,58 +550,68 @@ function MessageMarkdown({ } if (children.length) { - const content = children as string[] - const contentStr = content.join('') - const citationMatchRegex = /\[\[?citation:\s*\d+\]?\]/g - const textList = contentStr.split(citationMatchRegex) - const citationList = contentStr.match(citationMatchRegex) return ( -
- {textList.map((text, index) => { - const citation = citationList?.[index] - const citationNumberMatch = citation?.match(/\d+/) - const citationIndex = citationNumberMatch - ? parseInt(citationNumberMatch[0], 10) - : null - const source = - citationIndex !== null ? sources?.[citationIndex - 1] : null - const sourceUrl = source ? new URL(source.link) : null - return ( - - {text && {text}} - {source && ( - - - window.open(source.link)} - > - {citationIndex} +
+ {children.map((childrenItem, index) => { + if (typeof childrenItem === 'string') { + const citationMatchRegex = /\[\[?citation:\s*\d+\]?\]/g + const textList = childrenItem.split(citationMatchRegex) + const citationList = childrenItem.match(citationMatchRegex) + return ( + + {textList.map((text, index) => { + const citation = citationList?.[index] + const citationNumberMatch = citation?.match(/\d+/) + const citationIndex = citationNumberMatch + ? parseInt(citationNumberMatch[0], 10) + : null + const source = + citationIndex !== null + ? sources?.[citationIndex - 1] + : null + const sourceUrl = source ? new URL(source.link) : null + return ( + + {text && {text}} + {source && ( + + + window.open(source.link)} + > + {citationIndex} + + + +
+
+ +

+ {sourceUrl!.hostname} +

+
+

+ {source.title} +

+

+ {source.snippet} +

+
+
+
+ )}
- - -
-
- -

- {sourceUrl!.hostname} -

-
-

- {source.title} -

-

- {source.snippet} -

-
-
- - )} -
- ) + ) + })} + + ) + } + + return {childrenItem} })}
) diff --git a/ee/tabby-ui/components/textarea-search.tsx b/ee/tabby-ui/components/textarea-search.tsx new file mode 100644 index 000000000000..c172512f75d4 --- /dev/null +++ b/ee/tabby-ui/components/textarea-search.tsx @@ -0,0 +1,81 @@ +'use client' + +import { useEffect, useState } from 'react' +import TextareaAutosize from 'react-textarea-autosize' + +import { cn } from '@/lib/utils' + +import { IconArrowRight } from './ui/icons' + +export default function TextAreaSearch({ + onSearch, + className +}: { + onSearch: (value: string) => void + className?: string +}) { + const [isShow, setIsShow] = useState(false) + const [isFocus, setIsFocus] = useState(false) + const [value, setValue] = useState('') + + useEffect(() => { + // Ensure the textarea height remains consistent during rendering + setIsShow(true) + }, []) + + const onSearchKeyDown = (e: React.KeyboardEvent) => { + if (e.key === 'Enter' && !e.shiftKey) return e.preventDefault() + } + + const onSearchKeyUp = (e: React.KeyboardEvent) => { + if (e.key === 'Enter' && !e.shiftKey) { + return search() + } + } + + const search = () => { + if (!value) return + onSearch(value) + setValue('') + } + + return ( +
+ setIsFocus(true)} + onBlur={() => setIsFocus(false)} + onChange={e => setValue(e.target.value)} + value={value} + /> +
0 + } + )} + onClick={search} + > + +
+
+ ) +} diff --git a/ee/tabby-ui/components/ui/icons.tsx b/ee/tabby-ui/components/ui/icons.tsx index 18ba0c88b665..96195dd7fd65 100644 --- a/ee/tabby-ui/components/ui/icons.tsx +++ b/ee/tabby-ui/components/ui/icons.tsx @@ -9,6 +9,7 @@ import { GitFork, Layers2, Mail, + Search, Sparkles, Star } from 'lucide-react' @@ -1480,6 +1481,13 @@ function IconSparkles({ return } +function IconSearch({ + className, + ...props +}: React.ComponentProps) { + return +} + export { IconEdit, IconNextChat, @@ -1558,5 +1566,6 @@ export { IconVSCode, IconJetBrains, IconLayers, - IconSparkles + IconSparkles, + IconSearch } diff --git a/ee/tabby-ui/lib/constants/index.ts b/ee/tabby-ui/lib/constants/index.ts index 7806dd822185..e74797c71689 100644 --- a/ee/tabby-ui/lib/constants/index.ts +++ b/ee/tabby-ui/lib/constants/index.ts @@ -3,5 +3,6 @@ export const PLACEHOLDER_EMAIL_FORM = 'name@yourcompany.com' export const DEFAULT_PAGE_SIZE = 20 export const SESSION_STORAGE_KEY = { - DEMO_AUTO_LOGIN: '_tabby_demo_autologin' + DEMO_AUTO_LOGIN: '_tabby_demo_autologin', + SEARCH_INITIAL_MSG: '_tabby_search_initial_msg' }