AdvisorSignalDetail.tsx107 lines · main
| 1 | import { useParams } from 'common' |
| 2 | import { EyeOff, Globe } from 'lucide-react' |
| 3 | import Link from 'next/link' |
| 4 | import { Button, Tooltip, TooltipContent, TooltipTrigger } from 'ui' |
| 5 | |
| 6 | import type { AdvisorSignalItem } from './AdvisorPanel.types' |
| 7 | import { useAdvisorSignals } from './useAdvisorSignals' |
| 8 | import { SIDEBAR_KEYS } from '@/components/layouts/ProjectLayout/LayoutSidebar/LayoutSidebarProvider' |
| 9 | import { AiAssistantDropdown } from '@/components/ui/AiAssistantDropdown' |
| 10 | import { InlineLink } from '@/components/ui/InlineLink' |
| 11 | import { useAdvisorStateSnapshot } from '@/state/advisor-state' |
| 12 | import { useAiAssistantStateSnapshot } from '@/state/ai-assistant-state' |
| 13 | import { useSidebarManagerSnapshot } from '@/state/sidebar-manager-state' |
| 14 | |
| 15 | interface AdvisorSignalDetailProps { |
| 16 | item: AdvisorSignalItem |
| 17 | } |
| 18 | |
| 19 | const buildSignalAssistantPrompt = (item: AdvisorSignalItem) => { |
| 20 | return [ |
| 21 | `I'm reviewing an Advisor signal for a banned IP address: ${item.sourceData.ip}.`, |
| 22 | item.description ?? item.summary, |
| 23 | 'Help me assess whether this ban should remain in place, what I should investigate before removing it, and what the safest next step is.', |
| 24 | 'Please include when it is reasonable to dismiss this signal versus remove the ban.', |
| 25 | ].join('\n\n') |
| 26 | } |
| 27 | |
| 28 | export const AdvisorSignalDetail = ({ item }: AdvisorSignalDetailProps) => { |
| 29 | const { ref: projectRef } = useParams() |
| 30 | |
| 31 | const snap = useAiAssistantStateSnapshot() |
| 32 | const { openSidebar } = useSidebarManagerSnapshot() |
| 33 | const { setSelectedItem } = useAdvisorStateSnapshot() |
| 34 | const { dismissSignal } = useAdvisorSignals({ projectRef }) |
| 35 | |
| 36 | const issueDescription = ( |
| 37 | <> |
| 38 | The IP address <code className="text-code-inline">{item.sourceData.ip}</code> is temporarily |
| 39 | blocked because of suspicious traffic or repeated failed password attempts. If this block is |
| 40 | expected, you can dismiss this signal or remove the ban. |
| 41 | </> |
| 42 | ) |
| 43 | |
| 44 | const handleAskAssistant = () => { |
| 45 | openSidebar(SIDEBAR_KEYS.AI_ASSISTANT) |
| 46 | snap.newChat({ |
| 47 | name: `Review ${item.title.toLowerCase()}`, |
| 48 | initialInput: buildSignalAssistantPrompt(item), |
| 49 | }) |
| 50 | } |
| 51 | |
| 52 | const onDismissSignal = () => { |
| 53 | dismissSignal(item.dismissalKey) |
| 54 | setSelectedItem(undefined) |
| 55 | } |
| 56 | |
| 57 | return ( |
| 58 | <div> |
| 59 | <h3 className="text-sm mb-2">Entity</h3> |
| 60 | <Tooltip> |
| 61 | <TooltipTrigger asChild> |
| 62 | <div className="flex items-center gap-2 px-2 py-0.5 bg-surface-200 border rounded-lg text-sm mb-6 w-fit"> |
| 63 | <span className="flex items-center text-foreground-muted" aria-hidden="true"> |
| 64 | <Globe size={15} className="text-foreground-muted" /> |
| 65 | </span> |
| 66 | <span>{item.sourceData.ip}</span> |
| 67 | </div> |
| 68 | </TooltipTrigger> |
| 69 | <TooltipContent side="bottom">IP address currently blocked by network bans</TooltipContent> |
| 70 | </Tooltip> |
| 71 | |
| 72 | <h3 className="text-sm mb-2">Issue</h3> |
| 73 | <p className="text-sm text-foreground-light mb-6"> |
| 74 | {issueDescription}{' '} |
| 75 | {item.docsUrl !== undefined && ( |
| 76 | <> |
| 77 | <InlineLink href={item.docsUrl}>Learn more</InlineLink>. |
| 78 | </> |
| 79 | )} |
| 80 | </p> |
| 81 | |
| 82 | <h3 className="text-sm mb-2">Resolve</h3> |
| 83 | <div className="flex items-center gap-2"> |
| 84 | <AiAssistantDropdown |
| 85 | label="Ask Assistant" |
| 86 | buildPrompt={() => buildSignalAssistantPrompt(item)} |
| 87 | onOpenAssistant={handleAskAssistant} |
| 88 | telemetrySource="advisor_signal_detail" |
| 89 | /> |
| 90 | {item.actions.map((action) => ( |
| 91 | <Button key={`${item.dismissalKey}-${action.href}`} type="default" asChild> |
| 92 | <Link href={action.href}> |
| 93 | <span className="flex items-center gap-2">{action.label}</span> |
| 94 | </Link> |
| 95 | </Button> |
| 96 | ))} |
| 97 | <Button |
| 98 | type="default" |
| 99 | icon={<EyeOff size={14} strokeWidth={1.5} />} |
| 100 | onClick={onDismissSignal} |
| 101 | > |
| 102 | Dismiss |
| 103 | </Button> |
| 104 | </div> |
| 105 | </div> |
| 106 | ) |
| 107 | } |