ErrorCodeTooltip.tsx130 lines · main
| 1 | import { ExternalLink } from 'lucide-react' |
| 2 | import { useTheme } from 'next-themes' |
| 3 | import Image from 'next/image' |
| 4 | import { useState } from 'react' |
| 5 | import { cn, HoverCard, HoverCardContent, HoverCardTrigger, InfoIcon } from 'ui' |
| 6 | import { ShimmeringLoader } from 'ui-patterns/ShimmeringLoader' |
| 7 | |
| 8 | import { getErrorCodeInfo } from './ErrorCodeTooltip.utils' |
| 9 | import { SIDEBAR_KEYS } from '@/components/layouts/ProjectLayout/LayoutSidebar/LayoutSidebarProvider' |
| 10 | import { AiAssistantDropdown } from '@/components/ui/AiAssistantDropdown' |
| 11 | import { useErrorCodesQuery } from '@/data/content-api/docs-error-codes-query' |
| 12 | import { Service } from '@/data/graphql/graphql' |
| 13 | import { BASE_PATH } from '@/lib/constants' |
| 14 | import { useAiAssistantStateSnapshot } from '@/state/ai-assistant-state' |
| 15 | import { useSidebarManagerSnapshot } from '@/state/sidebar-manager-state' |
| 16 | |
| 17 | interface ErrorCodeTooltipProps { |
| 18 | errorCode: string |
| 19 | service?: Service |
| 20 | children: React.ReactNode |
| 21 | } |
| 22 | |
| 23 | export const ErrorCodeTooltip = ({ errorCode, service, children }: ErrorCodeTooltipProps) => { |
| 24 | const [isOpen, setIsOpen] = useState(false) |
| 25 | const { resolvedTheme } = useTheme() |
| 26 | const snap = useAiAssistantStateSnapshot() |
| 27 | const { openSidebar } = useSidebarManagerSnapshot() |
| 28 | |
| 29 | const { definition, docsUrl: sharedDocsUrl } = getErrorCodeInfo(errorCode, service) |
| 30 | |
| 31 | const { data, isPending } = useErrorCodesQuery( |
| 32 | { code: errorCode, service }, |
| 33 | { enabled: isOpen && !definition } |
| 34 | ) |
| 35 | |
| 36 | const errors = data?.errors?.nodes?.filter((e) => !!e.message) ?? [] |
| 37 | |
| 38 | const description = definition?.description ?? errors[0]?.message |
| 39 | const docsUrl = sharedDocsUrl |
| 40 | |
| 41 | const buildPrompt = () => { |
| 42 | const servicePart = service ? ` in Briven ${service}` : '' |
| 43 | const descriptionPart = description ? `\n\nError description: ${description}` : '' |
| 44 | return `I'm encountering error code \`${errorCode}\`${servicePart}.${descriptionPart}\n\nCan you explain what this error means and suggest steps to fix it?` |
| 45 | } |
| 46 | |
| 47 | const handleOpenAssistant = () => { |
| 48 | openSidebar(SIDEBAR_KEYS.AI_ASSISTANT) |
| 49 | snap.newChat({ |
| 50 | name: `Fix error ${errorCode}`, |
| 51 | initialMessage: buildPrompt(), |
| 52 | }) |
| 53 | } |
| 54 | |
| 55 | return ( |
| 56 | <HoverCard open={isOpen} onOpenChange={setIsOpen} openDelay={200} closeDelay={100}> |
| 57 | <HoverCardTrigger asChild> |
| 58 | <span className="inline-flex items-center gap-1 cursor-default"> |
| 59 | {children} |
| 60 | <InfoIcon hideBackground className="w-3.5 h-3.5 shrink-0 fill-foreground-muted" /> |
| 61 | </span> |
| 62 | </HoverCardTrigger> |
| 63 | <HoverCardContent side="top" align="center" className="w-[360px] p-0 overflow-hidden"> |
| 64 | <div className="flex flex-col"> |
| 65 | <div className="px-4 pt-3 pb-2.5 border-b border-border"> |
| 66 | <p className="font-mono text-xs uppercase text-foreground-light truncate max-w-[40ch]"> |
| 67 | {errorCode} |
| 68 | </p> |
| 69 | </div> |
| 70 | |
| 71 | <div className="px-4 py-3 space-y-2"> |
| 72 | {isPending && !definition ? ( |
| 73 | <div className="space-y-1.5"> |
| 74 | <ShimmeringLoader className="w-full" /> |
| 75 | <ShimmeringLoader className="w-4/5" /> |
| 76 | <ShimmeringLoader className="w-3/5" /> |
| 77 | </div> |
| 78 | ) : !description ? ( |
| 79 | <p className="text-sm text-foreground-lighter"> |
| 80 | No description available for this error code. |
| 81 | </p> |
| 82 | ) : ( |
| 83 | <p className="text-sm text-foreground leading-relaxed">{description}</p> |
| 84 | )} |
| 85 | </div> |
| 86 | |
| 87 | <div |
| 88 | className={cn( |
| 89 | 'border-t border-border px-4 py-2.5 flex items-center', |
| 90 | docsUrl ? 'justify-between' : 'justify-end' |
| 91 | )} |
| 92 | > |
| 93 | {docsUrl && ( |
| 94 | <div className="flex items-center gap-1.5"> |
| 95 | <Image |
| 96 | src={ |
| 97 | resolvedTheme?.includes('dark') |
| 98 | ? `${BASE_PATH}/img/briven-dark.svg` |
| 99 | : `${BASE_PATH}/img/briven-light.svg` |
| 100 | } |
| 101 | alt="Briven" |
| 102 | height={14} |
| 103 | width={72} |
| 104 | /> |
| 105 | <span className="font-mono text-[11px] font-semibold text-brand tracking-wide"> |
| 106 | DOCS |
| 107 | </span> |
| 108 | </div> |
| 109 | )} |
| 110 | <AiAssistantDropdown |
| 111 | showExternalAI |
| 112 | label="Fix with AI" |
| 113 | buildPrompt={buildPrompt} |
| 114 | onOpenAssistant={handleOpenAssistant} |
| 115 | telemetrySource="error_code" |
| 116 | copyLabel="Copy Markdown" |
| 117 | additionalDropdownItems={[ |
| 118 | { |
| 119 | label: 'Go to Docs', |
| 120 | icon: <ExternalLink size={14} />, |
| 121 | href: docsUrl, |
| 122 | }, |
| 123 | ]} |
| 124 | /> |
| 125 | </div> |
| 126 | </div> |
| 127 | </HoverCardContent> |
| 128 | </HoverCard> |
| 129 | ) |
| 130 | } |