@@ -7,24 +7,21 @@ import {
77} from "@heroicons/react/20/solid" ;
88import { type FeedbackComment , KapaProvider , type QA , useChat } from "@kapaai/react-sdk" ;
99import { useSearchParams } from "@remix-run/react" ;
10+ import DOMPurify from "dompurify" ;
1011import { motion } from "framer-motion" ;
1112import { marked } from "marked" ;
12- import {
13- createContext ,
14- type ReactNode ,
15- useCallback ,
16- useContext ,
17- useEffect ,
18- useRef ,
19- useState ,
20- } from "react" ;
13+ import { useCallback , useEffect , useRef , useState } from "react" ;
14+ import { useTypedRouteLoaderData } from "remix-typedjson" ;
2115import { AISparkleIcon } from "~/assets/icons/AISparkleIcon" ;
2216import { SparkleListIcon } from "~/assets/icons/SparkleListIcon" ;
17+ import { useFeatures } from "~/hooks/useFeatures" ;
18+ import { type loader } from "~/root" ;
2319import { Button } from "./primitives/Buttons" ;
2420import { Callout } from "./primitives/Callout" ;
2521import { Dialog , DialogContent , DialogHeader , DialogTitle } from "./primitives/Dialog" ;
2622import { Header2 } from "./primitives/Headers" ;
2723import { Paragraph } from "./primitives/Paragraph" ;
24+ import { ShortcutKey } from "./primitives/ShortcutKey" ;
2825import { Spinner } from "./primitives/Spinner" ;
2926import {
3027 SimpleTooltip ,
@@ -33,31 +30,28 @@ import {
3330 TooltipProvider ,
3431 TooltipTrigger ,
3532} from "./primitives/Tooltip" ;
36- import DOMPurify from "dompurify" ;
3733
38- type AskAIContextType = {
39- isOpen : boolean ;
40- openAskAI : ( question ?: string ) => void ;
41- closeAskAI : ( ) => void ;
42- websiteId : string | null ;
43- } ;
34+ function useKapaWebsiteId ( ) {
35+ const routeMatch = useTypedRouteLoaderData < typeof loader > ( "root" ) ;
36+ return routeMatch ?. kapa . websiteId ;
37+ }
4438
45- const AskAIContext = createContext < AskAIContextType | null > ( null ) ;
39+ export function AskAI ( ) {
40+ const { isManagedCloud } = useFeatures ( ) ;
41+ const websiteId = useKapaWebsiteId ( ) ;
4642
47- export function useAskAI ( ) {
48- const context = useContext ( AskAIContext ) ;
49- if ( ! context ) {
50- throw new Error ( "useAskAI must be used within an AskAIProvider" ) ;
43+ if ( ! isManagedCloud || ! websiteId ) {
44+ return null ;
5145 }
52- return context ;
46+
47+ return < AskAIProvider websiteId = { websiteId } /> ;
5348}
5449
5550type AskAIProviderProps = {
56- children : ReactNode ;
57- websiteId : string | null ;
51+ websiteId : string ;
5852} ;
5953
60- export function AskAIProvider ( { children , websiteId } : AskAIProviderProps ) {
54+ function AskAIProvider ( { websiteId } : AskAIProviderProps ) {
6155 const [ isOpen , setIsOpen ] = useState ( false ) ;
6256 const [ initialQuery , setInitialQuery ] = useState < string | undefined > ( ) ;
6357 const [ searchParams , setSearchParams ] = useSearchParams ( ) ;
@@ -94,45 +88,57 @@ export function AskAIProvider({ children, websiteId }: AskAIProviderProps) {
9488 }
9589 } , [ searchParams . toString ( ) , openAskAI ] ) ;
9690
97- const contextValue : AskAIContextType = {
98- isOpen,
99- openAskAI,
100- closeAskAI,
101- websiteId,
102- } ;
103-
104- if ( ! websiteId ) {
105- return < AskAIContext . Provider value = { contextValue } > { children } </ AskAIContext . Provider > ;
106- }
107-
10891 return (
109- < AskAIContext . Provider value = { contextValue } >
110- < KapaProvider
111- integrationId = { websiteId }
112- callbacks = { {
113- askAI : {
114- onQuerySubmit : ( ) => openAskAI ( ) ,
115- onAnswerGenerationCompleted : ( ) => openAskAI ( ) ,
116- } ,
117- } }
118- botProtectionMechanism = "hcaptcha"
119- >
120- { children }
121- < AskAIDialog initialQuery = { initialQuery } isOpen = { isOpen } onOpenChange = { setIsOpen } />
122- </ KapaProvider >
123- </ AskAIContext . Provider >
92+ < KapaProvider
93+ integrationId = { websiteId }
94+ callbacks = { {
95+ askAI : {
96+ onQuerySubmit : ( ) => openAskAI ( ) ,
97+ onAnswerGenerationCompleted : ( ) => openAskAI ( ) ,
98+ } ,
99+ } }
100+ botProtectionMechanism = "hcaptcha"
101+ >
102+ < TooltipProvider disableHoverableContent >
103+ < Tooltip >
104+ < TooltipTrigger asChild >
105+ < div className = "inline-flex" >
106+ < Button
107+ variant = "small-menu-item"
108+ data-action = "ask-ai"
109+ shortcut = { { modifiers : [ "mod" ] , key : "/" , enabledOnInputElements : true } }
110+ hideShortcutKey
111+ data-modal-override-open-class-ask-ai = "true"
112+ onClick = { ( ) => openAskAI ( ) }
113+ >
114+ < AISparkleIcon className = "size-5" />
115+ </ Button >
116+ </ div >
117+ </ TooltipTrigger >
118+ < TooltipContent side = "top" className = "flex items-center gap-1 py-1.5 pl-2.5 pr-2 text-xs" >
119+ Ask AI
120+ < ShortcutKey shortcut = { { modifiers : [ "mod" ] , key : "/" } } variant = "medium/bright" />
121+ </ TooltipContent >
122+ </ Tooltip >
123+ </ TooltipProvider >
124+ < AskAIDialog
125+ initialQuery = { initialQuery }
126+ isOpen = { isOpen }
127+ onOpenChange = { setIsOpen }
128+ closeAskAI = { closeAskAI }
129+ />
130+ </ KapaProvider >
124131 ) ;
125132}
126133
127134type AskAIDialogProps = {
128135 initialQuery ?: string ;
129136 isOpen : boolean ;
130137 onOpenChange : ( open : boolean ) => void ;
138+ closeAskAI : ( ) => void ;
131139} ;
132140
133- function AskAIDialog ( { initialQuery, isOpen, onOpenChange } : AskAIDialogProps ) {
134- const { closeAskAI } = useAskAI ( ) ;
135-
141+ function AskAIDialog ( { initialQuery, isOpen, onOpenChange, closeAskAI } : AskAIDialogProps ) {
136142 const handleOpenChange = ( open : boolean ) => {
137143 if ( ! open ) {
138144 closeAskAI ( ) ;
@@ -514,29 +520,3 @@ function GradientSpinnerBackground({
514520 </ div >
515521 ) ;
516522}
517-
518- export function AskAIButton ( { question } : { question ?: string } ) {
519- const { openAskAI } = useAskAI ( ) ;
520-
521- return (
522- < TooltipProvider disableHoverableContent >
523- < Tooltip >
524- < TooltipTrigger asChild >
525- < div className = "inline-flex" >
526- < Button
527- variant = "minimal/small"
528- onClick = { ( ) => openAskAI ( question ) }
529- className = "pl-0.5 pr-1"
530- data-action = "ask-ai"
531- >
532- < AISparkleIcon className = "size-5" />
533- </ Button >
534- </ div >
535- </ TooltipTrigger >
536- < TooltipContent side = "top" className = "flex items-center gap-1 px-2 py-1.5 text-xs" >
537- Ask AI
538- </ TooltipContent >
539- </ Tooltip >
540- </ TooltipProvider >
541- ) ;
542- }
0 commit comments