diff --git a/ee/tabby-ui/app/page/components/doc-select.tsx b/ee/tabby-ui/app/page/components/doc-select.tsx new file mode 100644 index 000000000000..d4c26ee6129f --- /dev/null +++ b/ee/tabby-ui/app/page/components/doc-select.tsx @@ -0,0 +1,160 @@ +import { useRef, useState } from 'react' + +import { ContextInfo } from '@/lib/gql/generates/graphql' +import { cn } from '@/lib/utils' +import { Button } from '@/components/ui/button' +import { + Command, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, + CommandList, + CommandSeparator +} from '@/components/ui/command' +import { IconCheck, IconEmojiBook } from '@/components/ui/icons' +import { + Popover, + PopoverContent, + PopoverTrigger +} from '@/components/ui/popover' +import { Skeleton } from '@/components/ui/skeleton' +import LoadingWrapper from '@/components/loading-wrapper' +import { SourceIcon } from '@/components/source-icon' + +interface RepoSelectProps { + docs: ContextInfo['sources'] | undefined + value: string | undefined + onChange: (v: string | undefined) => void + isInitializing?: boolean +} + +export function DocSelect({ + docs, + value, + onChange, + isInitializing +}: RepoSelectProps) { + const [open, setOpen] = useState(false) + const commandListRef = useRef(null) + + const onSelectRepo = (v: string) => { + onChange(v) + } + + const scrollCommandListToTop = () => { + requestAnimationFrame(() => { + if (commandListRef.current) { + commandListRef.current.scrollTop = 0 + } + }) + } + + const onSearchChange = () => { + scrollCommandListToTop() + } + + const selectedDoc = value + ? docs?.find(repo => repo.sourceId === value) + : undefined + const selectedRepoName = selectedDoc?.sourceName + + // if there's no repo, hide the repo select + if (!isInitializing && !docs?.length) return null + + return ( + + + + } + > + + + + + + + + + No context found + + {docs?.map(repo => { + const isSelected = repo.sourceId === value + + return ( + { + onSelectRepo(repo.sourceId) + setOpen(false) + }} + title={repo.sourceName} + > + +
+ +
+ {repo.sourceName} +
+
+
+ ) + })} +
+
+ + + { + onChange(undefined) + setOpen(false) + }} + > + Clear + + +
+
+
+
+ ) +} diff --git a/ee/tabby-ui/app/page/components/page.tsx b/ee/tabby-ui/app/page/components/page.tsx index acd7e0468085..45b404206363 100644 --- a/ee/tabby-ui/app/page/components/page.tsx +++ b/ee/tabby-ui/app/page/components/page.tsx @@ -59,7 +59,8 @@ import { cn, getMentionsFromText, getThreadRunContextsFromMentions, - getTitleFromMessages + getTitleFromMessages, + isCodeSourceContext } from '@/lib/utils' import { Button, buttonVariants } from '@/components/ui/button' import { @@ -79,9 +80,11 @@ import NotFoundPage from '@/components/not-found-page' import TextAreaSearch from '@/components/textarea-search' import { MyAvatar } from '@/components/user-avatar' +import { DocSelect } from './doc-select' import { Header } from './header' import { MessagesSkeleton } from './messages-skeleton' import { Navbar } from './nav-bar' +import { RepoSelect } from './repo-select' import { SectionContent } from './section-content' import { SectionTitle } from './section-title' @@ -210,6 +213,18 @@ export function Page() { query: contextInfoQuery }) + const repos = useMemo(() => { + return contextInfoData?.contextInfo?.sources.filter(x => + isCodeSourceContext(x.sourceKind) + ) + }, [contextInfoData?.contextInfo?.sources]) + + const docs = useMemo(() => { + return contextInfoData?.contextInfo?.sources.filter( + x => !isCodeSourceContext(x.sourceKind) + ) + }, [contextInfoData?.contextInfo?.sources]) + const [afterCursor, setAfterCursor] = useState() const [{ data: threadData, fetching: fetchingThread, error: threadError }] = @@ -856,6 +871,8 @@ export function Page() { + + void + isInitializing?: boolean +} + +export function RepoSelect({ + repos, + value, + onChange, + isInitializing +}: RepoSelectProps) { + const [open, setOpen] = useState(false) + const commandListRef = useRef(null) + + const onSelectRepo = (v: string) => { + onChange(v) + } + + const scrollCommandListToTop = () => { + requestAnimationFrame(() => { + if (commandListRef.current) { + commandListRef.current.scrollTop = 0 + } + }) + } + + const onSearchChange = () => { + scrollCommandListToTop() + } + + const selectedRepo = value + ? repos?.find(repo => repo.sourceId === value) + : undefined + const selectedRepoName = selectedRepo?.sourceName + + // if there's no repo, hide the repo select + if (!isInitializing && !repos?.length) return null + + return ( + + + + } + > + + + + + + + + + No context found + + {repos?.map(repo => { + const isSelected = repo.sourceId === value + + return ( + { + onSelectRepo(repo.sourceId) + setOpen(false) + }} + title={repo.sourceName} + > + +
+ +
+ {repo.sourceName} +
+
+
+ ) + })} +
+
+ + + { + onChange(undefined) + setOpen(false) + }} + > + Clear + + +
+
+
+
+ ) +} diff --git a/ee/tabby-ui/app/page/components/section-title.tsx b/ee/tabby-ui/app/page/components/section-title.tsx index 66c6e5ef81ef..e4a1086c2c04 100644 --- a/ee/tabby-ui/app/page/components/section-title.tsx +++ b/ee/tabby-ui/app/page/components/section-title.tsx @@ -1,8 +1,9 @@ import { HTMLAttributes, useContext } from 'react' import { cn } from '@/lib/utils' +import { Badge } from '@/components/ui/badge' import { Button } from '@/components/ui/button' -import { IconEdit } from '@/components/ui/icons' +import { IconEdit, IconEmojiBook, IconGithub } from '@/components/ui/icons' import { ChatContext } from '@/components/chat/chat' import { MessageMarkdown } from '@/components/message-markdown' @@ -20,25 +21,42 @@ export function SectionTitle({ const { fetchingContextInfo, mode } = useContext(PageContext) const { supportsOnApplyInEditorV2 } = useContext(ChatContext) return ( -
- - {mode === 'edit' && ( - - )} +
+
+ {/* todo use markdown? */} + + {mode === 'edit' && ( + + )} +
+
+ + + TabbyML/tabby + + + + Tailwindcss + + + + Pundit + +
) }