diff --git a/ee/tabby-ui/app/(home)/components/thread-feeds.tsx b/ee/tabby-ui/app/(home)/components/thread-feeds.tsx index f589d47d935d..1023660e52bf 100644 --- a/ee/tabby-ui/app/(home)/components/thread-feeds.tsx +++ b/ee/tabby-ui/app/(home)/components/thread-feeds.tsx @@ -312,7 +312,7 @@ function ThreadItem({ data }: ThreadItemProps) { return (
diff --git a/ee/tabby-ui/app/search/components/assistant-message-section.tsx b/ee/tabby-ui/app/search/components/assistant-message-section.tsx index fd21467870f3..ee47857140bc 100644 --- a/ee/tabby-ui/app/search/components/assistant-message-section.tsx +++ b/ee/tabby-ui/app/search/components/assistant-message-section.tsx @@ -38,6 +38,11 @@ import { FormItem, FormMessage } from '@/components/ui/form' +import { + HoverCard, + HoverCardContent, + HoverCardTrigger +} from '@/components/ui/hover-card' import { IconBlocks, IconBug, @@ -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' @@ -495,33 +502,40 @@ function SourceCard({ } return ( - - -
window.open(source.link)} - > - -
-
- + -

Score: {source?.extra?.score ?? '-'}

-
-
+ + +
window.open(source.link)} + > + +
+
+
+ +

Score: {source?.extra?.score ?? '-'}

+
+ + + + + ) } @@ -536,6 +550,10 @@ 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 (
@@ -543,17 +561,25 @@ function SourceCardContent({

{source.title}

-

- {normalizedText(getContent(source))} -

+ + {showAvatar && ( +
+ +

+ {author?.name} +

+
+ )} + {(!showAvatar || showMore) && ( +

+ {normalizedText(getContent(source))} +

+ )}
diff --git a/ee/tabby-ui/app/search/components/search.tsx b/ee/tabby-ui/app/search/components/search.tsx index fa2a6b294817..96a934154112 100644 --- a/ee/tabby-ui/app/search/components/search.tsx +++ b/ee/tabby-ui/app/search/components/search.tsx @@ -771,6 +771,7 @@ export function Search() { error={ (formatedThreadError || threadMessagesError) as ExtendedCombinedError } + threadIdFromURL={threadIdFromURL} /> ) } @@ -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' @@ -999,7 +1004,7 @@ function ThreadMessagesErrorView({ error }: ThreadMessagesErrorViewProps) { return (
-
+
diff --git a/ee/tabby-ui/components/message-markdown/doc-detail-view.tsx b/ee/tabby-ui/components/message-markdown/doc-detail-view.tsx new file mode 100644 index 000000000000..999a41b2d1f8 --- /dev/null +++ b/ee/tabby-ui/components/message-markdown/doc-detail-view.tsx @@ -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 ( +
+
+
+ +

{sourceUrl!.hostname}

+
+

window.open(relevantDocument.link)} + > + {relevantDocument.title} +

+
+ {isIssue && ( + + )} + {isPR && ( + + )} +
+

+ {normalizedText(getContent(relevantDocument))} +

+
+
+ ) +} + +function PullDocInfoView({ + merged, + user +}: { + merged: boolean + user: Maybe<{ id: string; email: string; name: string }> | undefined +}) { + return ( +
+ +
+ {!!user && ( + <> + + + {user.name || user.email} + + + )} +
+
+ ) +} + +function IssueDocInfoView({ + closed, + user +}: { + closed: boolean + user: Maybe<{ id: string; email: string; name: string }> | undefined +}) { + return ( +
+ +
+ {!!user && ( + <> + + + {user.name || user.email} + + + )} +
+
+ ) +} + +function IssueStateBadge({ closed }: { closed: boolean }) { + return ( + + {closed ? ( + + ) : ( + + )} + {closed ? 'Closed' : 'Open'} + + ) +} + +function PRStateBadge({ merged }: { merged: boolean }) { + return ( + + {merged ? ( + + ) : ( + + )} + {merged ? 'Merged' : 'Open'} + + ) +} + +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 +} diff --git a/ee/tabby-ui/components/message-markdown/index.tsx b/ee/tabby-ui/components/message-markdown/index.tsx index 248241d2cde3..c93fc3d8647d 100644 --- a/ee/tabby-ui/components/message-markdown/index.tsx +++ b/ee/tabby-ui/components/message-markdown/index.tsx @@ -1,10 +1,5 @@ import { ReactNode, useContext, useMemo, useState } from 'react' -import Image from 'next/image' -import defaultFavicon from '@/assets/default-favicon.png' -import DOMPurify from 'dompurify' -import he from 'he' import { compact, isNil } from 'lodash-es' -import { marked } from 'marked' import remarkGfm from 'remark-gfm' import remarkMath from 'remark-math' @@ -14,7 +9,7 @@ import { MessageAttachmentClientCode } from '@/lib/gql/generates/graphql' import { AttachmentCodeItem, AttachmentDocItem } from '@/lib/types' -import { cn, getContent } from '@/lib/utils' +import { cn } from '@/lib/utils' import { HoverCard, HoverCardContent, @@ -37,15 +32,9 @@ import { } from '@/lib/constants/regex' import { Mention } from '../mention-tag' -import { Badge } from '../ui/badge' -import { - IconCheckCircled, - IconCircleDot, - IconGitMerge, - IconGitPullRequest -} from '../ui/icons' import { Skeleton } from '../ui/skeleton' import { CodeElement } from './code' +import { DocDetailView } from './doc-detail-view' import { MessageMarkdownContext } from './markdown-context' type RelevantDocItem = { @@ -61,17 +50,6 @@ type RelevantCodeItem = { type MessageAttachments = Array -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 -} - export interface MessageMarkdownProps { message: string headline?: boolean @@ -381,12 +359,8 @@ function RelevantDocumentBadge({ relevantDocument: AttachmentDocItem citationIndex: number }) { - const sourceUrl = relevantDocument ? new URL(relevantDocument.link) : null - const isIssue = relevantDocument?.__typename === 'MessageAttachmentIssueDoc' - const isPR = relevantDocument?.__typename === 'MessageAttachmentPullDoc' - return ( - + - -
-
- -

{sourceUrl!.hostname}

-
-

window.open(relevantDocument.link)} - > - {relevantDocument.title} -

-
- {isIssue && } - {isPR && } -
-

- {normalizedText(getContent(relevantDocument))} -

-
+ +
) @@ -453,78 +406,3 @@ function RelevantCodeBadge({ ) } - -export function SiteFavicon({ - hostname, - className -}: { - hostname: string - className?: string -}) { - const [isLoaded, setIsLoaded] = useState(false) - - const handleImageLoad = () => { - setIsLoaded(true) - } - - return ( -
- {hostname} - {hostname} -
- ) -} - -function IssueStateBadge({ closed }: { closed: boolean }) { - return ( - - {closed ? ( - - ) : ( - - )} - {closed ? 'Closed' : 'Open'} - - ) -} - -function PRStateBadge({ merged }: { merged: boolean }) { - return ( - - {merged ? ( - - ) : ( - - )} - {merged ? 'Merged' : 'Open'} - - ) -} diff --git a/ee/tabby-ui/components/site-favicon.tsx b/ee/tabby-ui/components/site-favicon.tsx new file mode 100644 index 000000000000..33b65bb37d81 --- /dev/null +++ b/ee/tabby-ui/components/site-favicon.tsx @@ -0,0 +1,48 @@ +import { useState } from 'react' +import Image from 'next/image' +import defaultFavicon from '@/assets/default-favicon.png' + +import { cn } from '@/lib/utils' + +export function SiteFavicon({ + hostname, + className +}: { + hostname: string + className?: string +}) { + const [isLoaded, setIsLoaded] = useState(false) + + const handleImageLoad = () => { + setIsLoaded(true) + } + + return ( +
+ {hostname} + {hostname} +
+ ) +} diff --git a/ee/tabby-ui/components/ui/hover-card.tsx b/ee/tabby-ui/components/ui/hover-card.tsx index 0ec79708ab32..0691ebcaafd0 100644 --- a/ee/tabby-ui/components/ui/hover-card.tsx +++ b/ee/tabby-ui/components/ui/hover-card.tsx @@ -8,7 +8,7 @@ import { cn } from '@/lib/utils' const HoverCard = HoverCardPrimitive.Root const HoverCardTrigger = HoverCardPrimitive.Trigger - +const HoverCardPortal = HoverCardPrimitive.Portal const HoverCardContent = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef @@ -26,4 +26,4 @@ const HoverCardContent = React.forwardRef< )) HoverCardContent.displayName = HoverCardPrimitive.Content.displayName -export { HoverCard, HoverCardTrigger, HoverCardContent } +export { HoverCard, HoverCardTrigger, HoverCardContent, HoverCardPortal } diff --git a/ee/tabby-ui/lib/hooks/use-thread-run.ts b/ee/tabby-ui/lib/hooks/use-thread-run.ts index 0a78fe90aa91..ae8a264ac610 100644 --- a/ee/tabby-ui/lib/hooks/use-thread-run.ts +++ b/ee/tabby-ui/lib/hooks/use-thread-run.ts @@ -65,12 +65,22 @@ const CreateThreadAndRunSubscription = graphql(/* GraphQL */ ` ... on MessageAttachmentIssueDoc { title link + author { + id + email + name + } body closed } ... on MessageAttachmentPullDoc { title link + author { + id + email + name + } body merged } @@ -132,12 +142,22 @@ const CreateThreadRunSubscription = graphql(/* GraphQL */ ` ... on MessageAttachmentIssueDoc { title link + author { + id + email + name + } body closed } ... on MessageAttachmentPullDoc { title link + author { + id + email + name + } body merged } diff --git a/ee/tabby-ui/lib/tabby/query.ts b/ee/tabby-ui/lib/tabby/query.ts index 10375f3025da..c1088a2ddad1 100644 --- a/ee/tabby-ui/lib/tabby/query.ts +++ b/ee/tabby-ui/lib/tabby/query.ts @@ -422,12 +422,22 @@ export const listThreadMessages = graphql(/* GraphQL */ ` ... on MessageAttachmentIssueDoc { title link + author { + id + email + name + } body closed } ... on MessageAttachmentPullDoc { title link + author { + id + email + name + } body merged }