@@ -79,6 +79,8 @@ import { SideMenuSection } from "./SideMenuSection";
7979import { useShortcutKeys } from "~/hooks/useShortcutKeys" ;
8080import { AISparkleIcon } from "~/assets/icons/AISparkleIcon" ;
8181import { ShortcutKey } from "../primitives/ShortcutKey" ;
82+ import { useFeatures } from "~/hooks/useFeatures" ;
83+ import { useKapa } from "~/root" ;
8284
8385type SideMenuUser = Pick < User , "email" | "admin" > & { isImpersonating : boolean } ;
8486export type SideMenuProject = Pick <
@@ -109,17 +111,6 @@ export function SideMenu({
109111 const currentPlan = useCurrentPlan ( ) ;
110112 const { isConnected } = useDevPresence ( ) ;
111113 const isFreeUser = currentPlan ?. v3Subscription ?. isPaying === false ;
112- const buttonRef = useRef < HTMLButtonElement > ( null ) ;
113-
114- useShortcutKeys ( {
115- shortcut : { key : "a" , modifiers : [ "mod" , "shift" ] } ,
116- action : ( e ) => {
117- e . preventDefault ( ) ;
118- if ( buttonRef . current ) {
119- buttonRef . current . click ( ) ;
120- }
121- } ,
122- } ) ;
123114
124115 useEffect ( ( ) => {
125116 const handleScroll = ( ) => {
@@ -291,37 +282,7 @@ export function SideMenu({
291282 </ div >
292283 < div className = "flex flex-col gap-1 border-t border-grid-bright p-1" >
293284 < div className = "flex w-full items-center justify-between" >
294- < HelpAndFeedback />
295- < TooltipProvider disableHoverableContent >
296- < Tooltip >
297- < TooltipTrigger asChild >
298- < div >
299- < Button
300- ref = { buttonRef }
301- variant = "small-menu-item"
302- data-action = "ask-ai"
303- shortcut = { { modifiers : [ "mod" ] , key : "/" , enabledOnInputElements : true } }
304- hideShortcutKey
305- data-modal-override-open-class-ask-ai = "true"
306- onClick = { ( ) => {
307- if ( typeof window . Kapa === "function" ) {
308- window . Kapa ( "open" ) ;
309- }
310- } }
311- >
312- < AISparkleIcon className = "size-5" />
313- </ Button >
314- </ div >
315- </ TooltipTrigger >
316- < TooltipContent
317- side = "top"
318- className = "flex items-center gap-1 py-1.5 pl-2.5 pr-2 text-xs"
319- >
320- Ask AI
321- < ShortcutKey shortcut = { { modifiers : [ "mod" ] , key : "/" } } variant = "medium/bright" />
322- </ TooltipContent >
323- </ Tooltip >
324- </ TooltipProvider >
285+ < HelpAndAI />
325286 </ div >
326287 { isFreeUser && (
327288 < FreePlanUsage
@@ -584,3 +545,90 @@ function SelectorDivider() {
584545 </ svg >
585546 ) ;
586547}
548+
549+ function HelpAndAI ( ) {
550+ const features = useFeatures ( ) ;
551+ const kapa = useKapa ( ) ;
552+
553+ useEffect ( ( ) => {
554+ if ( ! features . isManagedCloud || ! kapa ?. websiteId ) return ;
555+
556+ loadScriptIfNotExists ( kapa . websiteId ) ;
557+
558+ const kapaInterval = setInterval ( ( ) => {
559+ if ( typeof window . Kapa === "function" ) {
560+ clearInterval ( kapaInterval ) ;
561+ window . Kapa ( "render" ) ;
562+ }
563+ } , 100 ) ;
564+
565+ // Clear interval on unmount to prevent memory leaks
566+ return ( ) => {
567+ clearInterval ( kapaInterval ) ;
568+ if ( typeof window . Kapa === "function" ) {
569+ window . Kapa ( "unmount" ) ;
570+ }
571+ } ;
572+ } , [ features . isManagedCloud , kapa ?. websiteId ] ) ;
573+
574+ return (
575+ < >
576+ < HelpAndFeedback />
577+ < TooltipProvider disableHoverableContent >
578+ < Tooltip >
579+ < TooltipTrigger asChild >
580+ < div >
581+ < Button
582+ variant = "small-menu-item"
583+ data-action = "ask-ai"
584+ shortcut = { { modifiers : [ "mod" ] , key : "/" , enabledOnInputElements : true } }
585+ hideShortcutKey
586+ data-modal-override-open-class-ask-ai = "true"
587+ onClick = { ( ) => {
588+ if ( typeof window . Kapa === "function" ) {
589+ window . Kapa ( "open" ) ;
590+ }
591+ } }
592+ >
593+ < AISparkleIcon className = "size-5" />
594+ </ Button >
595+ </ div >
596+ </ TooltipTrigger >
597+ < TooltipContent side = "top" className = "flex items-center gap-1 py-1.5 pl-2.5 pr-2 text-xs" >
598+ Ask AI
599+ < ShortcutKey shortcut = { { modifiers : [ "mod" ] , key : "/" } } variant = "medium/bright" />
600+ </ TooltipContent >
601+ </ Tooltip >
602+ </ TooltipProvider >
603+ </ >
604+ ) ;
605+ }
606+
607+ function loadScriptIfNotExists ( websiteId : string ) {
608+ const scriptSrc = "https://widget.kapa.ai/kapa-widget.bundle.js" ;
609+
610+ if ( document . querySelector ( `script[src="${ scriptSrc } "]` ) ) {
611+ return ;
612+ }
613+
614+ const script = document . createElement ( "script" ) ;
615+ script . async = true ;
616+ script . src = scriptSrc ;
617+
618+ const attributes = {
619+ "data-website-id" : websiteId ,
620+ "data-project-name" : "Trigger.dev" ,
621+ "data-project-color" : "#6366F1" ,
622+ "data-project-logo" : "https://content.trigger.dev/trigger-logo-triangle.png" ,
623+ "data-render-on-load" : "false" ,
624+ "data-button-hide" : "true" ,
625+ "data-modal-disclaimer-bg-color" : "#1A1B1F" ,
626+ "data-modal-disclaimer-text-color" : "#878C99" ,
627+ } ;
628+
629+ Object . entries ( attributes ) . forEach ( ( [ key , value ] ) => {
630+ script . setAttribute ( key , value ) ;
631+ } ) ;
632+
633+ document . head . appendChild ( script ) ;
634+ }
0 commit comments