Skip to content

Commit

Permalink
feat(ui): integrate author for github issues/PRs (#3500)
Browse files Browse the repository at this point in the history
* feat(ui): display information of pr/issue author

* update: add hover card

* update

* update: mock

* update: fetch user by email

* update

* update

* update

* update

* update

* [autofix.ci] apply automated fixes

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
  • Loading branch information
liangfung and autofix-ci[bot] authored Dec 10, 2024
1 parent 42300c4 commit 3df11d1
Show file tree
Hide file tree
Showing 9 changed files with 311 additions and 171 deletions.
2 changes: 1 addition & 1 deletion ee/tabby-ui/app/(home)/components/thread-feeds.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -312,7 +312,7 @@ function ThreadItem({ data }: ThreadItemProps) {

return (
<Link
href={title ? `/search/${titleSlug}-${threadId}` : ''}
href={title ? `/search/${titleSlug}-${threadId}` : `/search/${threadId}`}
onClick={onNavigateToThread}
>
<div className="transform-bg group flex-1 overflow-hidden rounded-lg px-3 py-2 hover:bg-accent">
Expand Down
104 changes: 65 additions & 39 deletions ee/tabby-ui/app/search/components/assistant-message-section.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,11 @@ import {
FormItem,
FormMessage
} from '@/components/ui/form'
import {
HoverCard,
HoverCardContent,
HoverCardTrigger
} from '@/components/ui/hover-card'
import {
IconBlocks,
IconBug,
Expand Down Expand Up @@ -65,9 +70,11 @@ import { CodeReferences } from '@/components/chat/code-references'
import { CopyButton } from '@/components/copy-button'
import {
ErrorMessageBlock,
MessageMarkdown,
SiteFavicon
MessageMarkdown
} from '@/components/message-markdown'
import { DocDetailView } from '@/components/message-markdown/doc-detail-view'
import { SiteFavicon } from '@/components/site-favicon'
import { UserAvatar } from '@/components/user-avatar'

import { ConversationMessage, SearchContext, SOURCE_CARD_STYLE } from './search'

Expand Down Expand Up @@ -495,33 +502,40 @@ function SourceCard({
}

return (
<Tooltip
open={devTooltipOpen}
onOpenChange={onOpenChange}
delayDuration={0}
>
<TooltipTrigger asChild>
<div
className="relative flex cursor-pointer flex-col justify-between rounded-lg border bg-card p-3 hover:bg-card/60"
style={{
height: showMore
? `${SOURCE_CARD_STYLE.expand}rem`
: `${SOURCE_CARD_STYLE.compress}rem`,
transition: 'all 0.25s ease-out'
}}
onClick={() => window.open(source.link)}
>
<SourceCardContent source={source} showMore={showMore} />
</div>
</TooltipTrigger>
<TooltipContent
align="start"
className="cursor-pointer p-2"
onClick={onTootipClick}
<HoverCard openDelay={100} closeDelay={100}>
<Tooltip
open={devTooltipOpen}
onOpenChange={onOpenChange}
delayDuration={0}
>
<p>Score: {source?.extra?.score ?? '-'}</p>
</TooltipContent>
</Tooltip>
<HoverCardTrigger asChild>
<TooltipTrigger asChild>
<div
className="relative flex cursor-pointer flex-col justify-between rounded-lg border bg-card p-3 hover:bg-card/60"
style={{
height: showMore
? `${SOURCE_CARD_STYLE.expand}rem`
: `${SOURCE_CARD_STYLE.compress}rem`,
transition: 'all 0.25s ease-out'
}}
onClick={() => window.open(source.link)}
>
<SourceCardContent source={source} showMore={showMore} />
</div>
</TooltipTrigger>
</HoverCardTrigger>
<TooltipContent
align="start"
className="cursor-pointer p-2"
onClick={onTootipClick}
>
<p>Score: {source?.extra?.score ?? '-'}</p>
</TooltipContent>
</Tooltip>
<HoverCardContent className="w-96 bg-background text-sm text-foreground dark:border-muted-foreground/60">
<DocDetailView relevantDocument={source} />
</HoverCardContent>
</HoverCard>
)
}

Expand All @@ -536,24 +550,36 @@ function SourceCardContent({

const isIssue = source.__typename === 'MessageAttachmentIssueDoc'
const isPR = source.__typename === 'MessageAttachmentPullDoc'
const author =
source.__typename === 'MessageAttachmentWebDoc' ? undefined : source.author

const showAvatar = (isIssue || isPR) && !!author

return (
<div className="flex flex-1 flex-col justify-between gap-y-1">
<div className="flex flex-col gap-y-0.5">
<p className="line-clamp-1 w-full overflow-hidden text-ellipsis break-all text-xs font-semibold">
{source.title}
</p>
<p
className={cn(
' w-full overflow-hidden text-ellipsis break-all text-xs text-muted-foreground',
{
'line-clamp-2': showMore,
'line-clamp-1': !showMore
}
)}
>
{normalizedText(getContent(source))}
</p>

{showAvatar && (
<div className="flex items-center gap-1 overflow-x-hidden">
<UserAvatar user={author} className="h-3.5 w-3.5 shrink-0" />
<p className="truncate text-xs font-medium text-muted-foreground">
{author?.name}
</p>
</div>
)}
{(!showAvatar || showMore) && (
<p
className={cn(
' w-full overflow-hidden text-ellipsis break-all text-xs text-muted-foreground',
!showAvatar && showMore ? 'line-clamp-2' : 'line-clamp-1'
)}
>
{normalizedText(getContent(source))}
</p>
)}
</div>
<div className="flex items-center text-xs text-muted-foreground">
<div className="flex w-full flex-1 items-center justify-between gap-1">
Expand Down
9 changes: 7 additions & 2 deletions ee/tabby-ui/app/search/components/search.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -771,6 +771,7 @@ export function Search() {
error={
(formatedThreadError || threadMessagesError) as ExtendedCombinedError
}
threadIdFromURL={threadIdFromURL}
/>
)
}
Expand Down Expand Up @@ -987,8 +988,12 @@ const updateThreadMessageMutation = graphql(/* GraphQL */ `

interface ThreadMessagesErrorViewProps {
error: ExtendedCombinedError
threadIdFromURL?: string
}
function ThreadMessagesErrorView({ error }: ThreadMessagesErrorViewProps) {
function ThreadMessagesErrorView({
error,
threadIdFromURL
}: ThreadMessagesErrorViewProps) {
let title = 'Something went wrong'
let description =
'Failed to fetch the thread, please refresh the page or start a new thread'
Expand All @@ -999,7 +1004,7 @@ function ThreadMessagesErrorView({ error }: ThreadMessagesErrorViewProps) {

return (
<div className="flex h-screen flex-col">
<Header />
<Header threadIdFromURL={threadIdFromURL} />
<div className="flex-1">
<div className="flex h-full flex-col items-center justify-center gap-2">
<div className="flex items-center gap-2">
Expand Down
153 changes: 153 additions & 0 deletions ee/tabby-ui/components/message-markdown/doc-detail-view.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
import DOMPurify from 'dompurify'
import he from 'he'
import { marked } from 'marked'

import { Maybe } from '@/lib/gql/generates/graphql'
import type { AttachmentDocItem } from '@/lib/types'
import { getContent } from '@/lib/utils'

import { SiteFavicon } from '../site-favicon'
import { Badge } from '../ui/badge'
import {
IconCheckCircled,
IconCircleDot,
IconGitMerge,
IconGitPullRequest
} from '../ui/icons'
import { UserAvatar } from '../user-avatar'

export function DocDetailView({
relevantDocument
}: {
relevantDocument: AttachmentDocItem
}) {
const sourceUrl = relevantDocument ? new URL(relevantDocument.link) : null
const isIssue = relevantDocument?.__typename === 'MessageAttachmentIssueDoc'
const isPR = relevantDocument?.__typename === 'MessageAttachmentPullDoc'
const author =
relevantDocument.__typename === 'MessageAttachmentWebDoc'
? undefined
: relevantDocument.author

return (
<div className="prose max-w-none break-words dark:prose-invert prose-p:leading-relaxed prose-pre:mt-1 prose-pre:p-0">
<div className="flex w-full flex-col gap-y-1 text-sm">
<div className="m-0 flex items-center space-x-1 text-xs leading-none text-muted-foreground">
<SiteFavicon
hostname={sourceUrl!.hostname}
className="m-0 mr-1 leading-none"
/>
<p className="m-0 leading-none">{sourceUrl!.hostname}</p>
</div>
<p
className="m-0 cursor-pointer font-bold leading-none transition-opacity hover:opacity-70"
onClick={() => window.open(relevantDocument.link)}
>
{relevantDocument.title}
</p>
<div className="mb-2 w-auto">
{isIssue && (
<IssueDocInfoView closed={relevantDocument.closed} user={author} />
)}
{isPR && (
<PullDocInfoView merged={relevantDocument.merged} user={author} />
)}
</div>
<p className="m-0 line-clamp-4 leading-none">
{normalizedText(getContent(relevantDocument))}
</p>
</div>
</div>
)
}

function PullDocInfoView({
merged,
user
}: {
merged: boolean
user: Maybe<{ id: string; email: string; name: string }> | undefined
}) {
return (
<div className="flex items-center gap-3">
<PRStateBadge merged={merged} />
<div className="flex flex-1 items-center gap-1.5">
{!!user && (
<>
<UserAvatar user={user} className="not-prose h-5 w-5 shrink-0" />
<span className="font-semibold text-muted-foreground">
{user.name || user.email}
</span>
</>
)}
</div>
</div>
)
}

function IssueDocInfoView({
closed,
user
}: {
closed: boolean
user: Maybe<{ id: string; email: string; name: string }> | undefined
}) {
return (
<div className="flex items-center gap-3">
<IssueStateBadge closed={closed} />
<div className="flex flex-1 items-center gap-1.5">
{!!user && (
<>
<UserAvatar user={user} className="not-prose h-5 w-5 shrink-0" />
<span className="font-semibold text-muted-foreground">
{user.name || user.email}
</span>
</>
)}
</div>
</div>
)
}

function IssueStateBadge({ closed }: { closed: boolean }) {
return (
<Badge
variant={closed ? 'default' : 'secondary'}
className="shrink-0 gap-1 py-1 text-xs"
>
{closed ? (
<IconCheckCircled className="h-3.5 w-3.5" />
) : (
<IconCircleDot className="h-3.5 w-3.5" />
)}
{closed ? 'Closed' : 'Open'}
</Badge>
)
}

function PRStateBadge({ merged }: { merged: boolean }) {
return (
<Badge
variant={merged ? 'default' : 'secondary'}
className="shrink-0 gap-1 py-1 text-xs"
>
{merged ? (
<IconGitMerge className="h-3.5 w-3.5" />
) : (
<IconGitPullRequest className="h-3.5 w-3.5" />
)}
{merged ? 'Merged' : 'Open'}
</Badge>
)
}

const normalizedText = (input: string) => {
const sanitizedHtml = DOMPurify.sanitize(input, {
ALLOWED_TAGS: [],
ALLOWED_ATTR: []
})
const parsed = marked.parse(sanitizedHtml) as string
const decoded = he.decode(parsed)
const plainText = decoded.replace(/<\/?[^>]+(>|$)/g, '')
return plainText
}
Loading

0 comments on commit 3df11d1

Please sign in to comment.