ErrorCodeTooltip.tsx 4.8 KB

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