Skip to content

Commit

Permalink
update: context
Browse files Browse the repository at this point in the history
  • Loading branch information
liangfung committed Jan 10, 2025
1 parent 81ebc75 commit fb8762b
Show file tree
Hide file tree
Showing 4 changed files with 383 additions and 21 deletions.
160 changes: 160 additions & 0 deletions ee/tabby-ui/app/page/components/doc-select.tsx
Original file line number Diff line number Diff line change
@@ -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<HTMLDivElement>(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 (
<LoadingWrapper
loading={isInitializing}
fallback={
<div className="w-full pl-2">
<Skeleton className="h-3 w-[10rem]" />
</div>
}
>
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger className="outline-none" asChild>
<Button
variant="ghost"
className="gap-2 px-1.5 py-1 font-normal"
title={selectedRepoName || 'select docs'}
>
<IconEmojiBook />
<div className="flex flex-1 items-center gap-1.5 truncate break-all">
<span
className={cn('truncate', {
'text-muted-foreground': !selectedRepoName
})}
>
{selectedRepoName || 'Docs'}
</span>
</div>
</Button>
</PopoverTrigger>
<PopoverContent
side="bottom"
align="start"
className="max-h-[50vh] min-w-[20vw] max-w-[80vw] overflow-x-hidden rounded-md border bg-popover p-2 pb-0 text-popover-foreground shadow animate-in"
>
<Command>
<CommandInput
placeholder="Search docs..."
onValueChange={onSearchChange}
/>
<CommandList className="max-h-[30vh]" ref={commandListRef}>
<CommandEmpty>No context found</CommandEmpty>
<CommandGroup>
{docs?.map(repo => {
const isSelected = repo.sourceId === value

return (
<CommandItem
key={repo.sourceId}
onSelect={() => {
onSelectRepo(repo.sourceId)
setOpen(false)
}}
title={repo.sourceName}
>
<IconCheck
className={cn(
'mr-1 shrink-0',
repo.sourceId === value ? 'opacity-100' : 'opacity-0'
)}
/>
<div className="flex flex-1 items-center gap-1 overflow-x-hidden">
<SourceIcon
kind={repo.sourceKind}
className="shrink-0"
/>
<div
className={cn('truncate', {
'font-semibold': isSelected
})}
>
{repo.sourceName}
</div>
</div>
</CommandItem>
)
})}
</CommandGroup>
</CommandList>
<CommandSeparator />
<CommandGroup>
<CommandItem
disabled={!value}
className="flex justify-center"
onSelect={() => {
onChange(undefined)
setOpen(false)
}}
>
Clear
</CommandItem>
</CommandGroup>
</Command>
</PopoverContent>
</Popover>
</LoadingWrapper>
)
}
19 changes: 18 additions & 1 deletion ee/tabby-ui/app/page/components/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,8 @@ import {
cn,
getMentionsFromText,
getThreadRunContextsFromMentions,
getTitleFromMessages
getTitleFromMessages,
isCodeSourceContext
} from '@/lib/utils'
import { Button, buttonVariants } from '@/components/ui/button'
import {
Expand All @@ -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'

Expand Down Expand Up @@ -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<string | undefined>()

const [{ data: threadData, fetching: fetchingThread, error: threadError }] =
Expand Down Expand Up @@ -856,6 +871,8 @@ export function Page() {
<Button size="icon" variant="ghost">
<IconSheet />
</Button>
<RepoSelect repos={repos} />
<DocSelect docs={docs} />
</div>
<TextAreaSearch
onSearch={onSubmitSearch}
Expand Down
167 changes: 167 additions & 0 deletions ee/tabby-ui/app/page/components/repo-select.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
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, IconFolderGit } 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 {
repos: ContextInfo['sources'] | undefined
value: string | undefined
onChange: (v: string | undefined) => void
isInitializing?: boolean
}

export function RepoSelect({
repos,
value,
onChange,
isInitializing
}: RepoSelectProps) {
const [open, setOpen] = useState(false)
const commandListRef = useRef<HTMLDivElement>(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 (
<LoadingWrapper
loading={isInitializing}
fallback={
<div className="w-full pl-2">
<Skeleton className="h-3 w-[10rem]" />
</div>
}
>
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger className="outline-none" asChild>
<Button
variant="ghost"
className="gap-2 px-1.5 py-1 font-normal"
title={selectedRepoName || 'select codebase'}
>
{selectedRepo ? (
<SourceIcon
kind={selectedRepo.sourceKind}
className="h-3.5 w-3.5 shrink-0"
/>
) : (
<IconFolderGit className="shrink-0" />
)}
<div className="flex flex-1 items-center gap-1.5 truncate break-all">
<span
className={cn('truncate', {
'text-muted-foreground': !selectedRepoName
})}
>
{selectedRepoName || 'Codebase'}
</span>
</div>
</Button>
</PopoverTrigger>
<PopoverContent
side="bottom"
align="start"
className="max-h-[50vh] min-w-[20vw] max-w-[80vw] overflow-x-hidden rounded-md border bg-popover p-2 pb-0 text-popover-foreground shadow animate-in"
>
<Command>
<CommandInput
placeholder="Search codebase..."
onValueChange={onSearchChange}
/>
<CommandList className="max-h-[30vh]" ref={commandListRef}>
<CommandEmpty>No context found</CommandEmpty>
<CommandGroup>
{repos?.map(repo => {
const isSelected = repo.sourceId === value

return (
<CommandItem
key={repo.sourceId}
onSelect={() => {
onSelectRepo(repo.sourceId)
setOpen(false)
}}
title={repo.sourceName}
>
<IconCheck
className={cn(
'mr-1 shrink-0',
repo.sourceId === value ? 'opacity-100' : 'opacity-0'
)}
/>
<div className="flex flex-1 items-center gap-1 overflow-x-hidden">
<SourceIcon
kind={repo.sourceKind}
className="shrink-0"
/>
<div
className={cn('truncate', {
'font-semibold': isSelected
})}
>
{repo.sourceName}
</div>
</div>
</CommandItem>
)
})}
</CommandGroup>
</CommandList>
<CommandSeparator />
<CommandGroup>
<CommandItem
disabled={!value}
className="flex justify-center"
onSelect={() => {
onChange(undefined)
setOpen(false)
}}
>
Clear
</CommandItem>
</CommandGroup>
</Command>
</PopoverContent>
</Popover>
</LoadingWrapper>
)
}
Loading

0 comments on commit fb8762b

Please sign in to comment.