AIAssistantHeader.tsx148 lines · main
| 1 | import { Clipboard, Ellipsis, Plus, Settings, X } from 'lucide-react' |
| 2 | import { useState } from 'react' |
| 3 | import { |
| 4 | AiIconAnimation, |
| 5 | Button, |
| 6 | copyToClipboard, |
| 7 | DropdownMenu, |
| 8 | DropdownMenuContent, |
| 9 | DropdownMenuItem, |
| 10 | DropdownMenuTrigger, |
| 11 | } from 'ui' |
| 12 | import { Admonition } from 'ui-patterns' |
| 13 | |
| 14 | import { ButtonTooltip } from '../ButtonTooltip' |
| 15 | import { AIAssistantChatSelector } from './AIAssistantChatSelector' |
| 16 | import { AIOptInModal } from './AIOptInModal' |
| 17 | import { useAiAssistantStateSnapshot } from '@/state/ai-assistant-state' |
| 18 | |
| 19 | interface AIAssistantHeaderProps { |
| 20 | isChatLoading: boolean |
| 21 | onNewChat: () => void |
| 22 | onCloseAssistant: () => void |
| 23 | showMetadataWarning: boolean |
| 24 | updatedOptInSinceMCP: boolean |
| 25 | isHipaaProjectDisallowed: boolean |
| 26 | aiOptInLevel: 'disabled' | 'schema' | 'full' | string | undefined |
| 27 | } |
| 28 | |
| 29 | export const AIAssistantHeader = ({ |
| 30 | isChatLoading, |
| 31 | onNewChat, |
| 32 | onCloseAssistant, |
| 33 | showMetadataWarning, |
| 34 | updatedOptInSinceMCP, |
| 35 | isHipaaProjectDisallowed, |
| 36 | aiOptInLevel, |
| 37 | }: AIAssistantHeaderProps) => { |
| 38 | const snap = useAiAssistantStateSnapshot() |
| 39 | const [isOptInModalOpen, setIsOptInModalOpen] = useState(false) |
| 40 | return ( |
| 41 | <div className="z-30 sticky top-0"> |
| 42 | <div className="border-b border-b-muted flex items-center bg gap-x-4 pl-4 pr-3 min-h-(--header-height)"> |
| 43 | <div className="text-sm flex-1 flex items-center"> |
| 44 | <AiIconAnimation size={20} allowHoverEffect={false} /> |
| 45 | <span className="text-border-stronger dark:text-border-strong ml-3"> |
| 46 | <svg |
| 47 | viewBox="0 0 24 24" |
| 48 | width="16" |
| 49 | height="16" |
| 50 | stroke="currentColor" |
| 51 | strokeWidth="1" |
| 52 | strokeLinecap="round" |
| 53 | strokeLinejoin="round" |
| 54 | fill="none" |
| 55 | shapeRendering="geometricPrecision" |
| 56 | > |
| 57 | <path d="M16 3.549L7.12 20.600" /> |
| 58 | </svg> |
| 59 | </span> |
| 60 | <AIAssistantChatSelector /> |
| 61 | </div> |
| 62 | <div className="flex items-center gap-x-4"> |
| 63 | <div className="flex items-center"> |
| 64 | <ButtonTooltip |
| 65 | type="text" |
| 66 | size="tiny" |
| 67 | icon={<Plus strokeWidth={1.5} />} |
| 68 | onClick={onNewChat} |
| 69 | className="h-7 w-7 p-0" |
| 70 | tooltip={{ content: { side: 'bottom', text: 'New chat' } }} |
| 71 | /> |
| 72 | <ButtonTooltip |
| 73 | type="text" |
| 74 | size="tiny" |
| 75 | icon={<Settings strokeWidth={1.5} />} |
| 76 | onClick={() => setIsOptInModalOpen(true)} |
| 77 | className="h-7 w-7 p-0" |
| 78 | disabled={isChatLoading} |
| 79 | tooltip={{ |
| 80 | content: { side: 'bottom', text: 'Permission settings' }, |
| 81 | }} |
| 82 | /> |
| 83 | <DropdownMenu> |
| 84 | <DropdownMenuTrigger asChild> |
| 85 | <ButtonTooltip |
| 86 | type="text" |
| 87 | size="tiny" |
| 88 | icon={<Ellipsis strokeWidth={1.5} />} |
| 89 | className="h-7 w-7 p-0" |
| 90 | tooltip={{ content: { side: 'bottom', text: 'More options' } }} |
| 91 | /> |
| 92 | </DropdownMenuTrigger> |
| 93 | <DropdownMenuContent align="end"> |
| 94 | <DropdownMenuItem |
| 95 | className="gap-x-2" |
| 96 | onClick={() => copyToClipboard(snap.activeChatId ?? '')} |
| 97 | > |
| 98 | <Clipboard size={14} strokeWidth={1.5} /> |
| 99 | <span>Copy chat ID</span> |
| 100 | </DropdownMenuItem> |
| 101 | </DropdownMenuContent> |
| 102 | </DropdownMenu> |
| 103 | <ButtonTooltip |
| 104 | type="text" |
| 105 | className="w-7 h-7" |
| 106 | onClick={onCloseAssistant} |
| 107 | icon={<X strokeWidth={1.5} />} |
| 108 | tooltip={{ content: { side: 'bottom', text: 'Close assistant' } }} |
| 109 | /> |
| 110 | </div> |
| 111 | </div> |
| 112 | </div> |
| 113 | {showMetadataWarning && ( |
| 114 | <Admonition |
| 115 | type="default" |
| 116 | title={ |
| 117 | !updatedOptInSinceMCP |
| 118 | ? 'The Assistant has just been updated to help you better!' |
| 119 | : isHipaaProjectDisallowed |
| 120 | ? 'Project metadata is not shared due to HIPAA' |
| 121 | : aiOptInLevel === 'disabled' |
| 122 | ? 'Project metadata is currently not shared' |
| 123 | : 'Limited metadata is shared to the Assistant' |
| 124 | } |
| 125 | description={ |
| 126 | !updatedOptInSinceMCP |
| 127 | ? 'You may now opt-in to share schema metadata and even logs for better results' |
| 128 | : isHipaaProjectDisallowed |
| 129 | ? 'Your organization has the HIPAA addon and will not send project metadata with your prompts for projects marked as HIPAA.' |
| 130 | : aiOptInLevel === 'disabled' |
| 131 | ? 'The Assistant can provide better answers if you opt-in to share schema metadata.' |
| 132 | : aiOptInLevel === 'schema' |
| 133 | ? 'Sharing query data in addition to schema can further improve responses. Update AI settings to enable this.' |
| 134 | : '' |
| 135 | } |
| 136 | className="border-0 border-b rounded-none bg-background" |
| 137 | > |
| 138 | {!isHipaaProjectDisallowed && ( |
| 139 | <Button type="default" className="w-fit mt-4" onClick={() => setIsOptInModalOpen(true)}> |
| 140 | Permission settings |
| 141 | </Button> |
| 142 | )} |
| 143 | </Admonition> |
| 144 | )} |
| 145 | <AIOptInModal visible={isOptInModalOpen} onCancel={() => setIsOptInModalOpen(false)} /> |
| 146 | </div> |
| 147 | ) |
| 148 | } |