docs: migrate Kapa (#10198)
* docs: migrate kapa * remove unused dependency * fix isbrowser
This commit is contained in:
@@ -55,4 +55,8 @@
|
|||||||
body[data-modal="opened"] {
|
body[data-modal="opened"] {
|
||||||
@apply !overflow-hidden;
|
@apply !overflow-hidden;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.grecaptcha-badge {
|
||||||
|
visibility: hidden;
|
||||||
}
|
}
|
||||||
@@ -32,4 +32,8 @@
|
|||||||
body[data-modal="opened"] {
|
body[data-modal="opened"] {
|
||||||
@apply !overflow-hidden;
|
@apply !overflow-hidden;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.grecaptcha-badge {
|
||||||
|
visibility: hidden;
|
||||||
}
|
}
|
||||||
@@ -35,4 +35,8 @@
|
|||||||
body[data-modal="opened"] {
|
body[data-modal="opened"] {
|
||||||
@apply !overflow-hidden;
|
@apply !overflow-hidden;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.grecaptcha-badge {
|
||||||
|
visibility: hidden;
|
||||||
}
|
}
|
||||||
@@ -41,3 +41,7 @@
|
|||||||
@apply !overflow-hidden text-ui-fg-base;
|
@apply !overflow-hidden text-ui-fg-base;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.grecaptcha-badge {
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
@@ -28,4 +28,8 @@
|
|||||||
body[data-modal="opened"] {
|
body[data-modal="opened"] {
|
||||||
@apply !overflow-hidden;
|
@apply !overflow-hidden;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.grecaptcha-badge {
|
||||||
|
visibility: hidden;
|
||||||
}
|
}
|
||||||
@@ -70,7 +70,6 @@
|
|||||||
"prism-react-renderer": "2.4.0",
|
"prism-react-renderer": "2.4.0",
|
||||||
"react": "rc",
|
"react": "rc",
|
||||||
"react-dom": "rc",
|
"react-dom": "rc",
|
||||||
"react-google-recaptcha": "^3.1.0",
|
|
||||||
"react-instantsearch": "^7.13.6",
|
"react-instantsearch": "^7.13.6",
|
||||||
"react-markdown": "^9.0.1",
|
"react-markdown": "^9.0.1",
|
||||||
"react-medium-image-zoom": "^5.2.10",
|
"react-medium-image-zoom": "^5.2.10",
|
||||||
|
|||||||
@@ -327,6 +327,7 @@ export const AiAssistant = () => {
|
|||||||
apply
|
apply
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
|
clickable={true}
|
||||||
>
|
>
|
||||||
<Badge variant="neutral">AI Assistant</Badge>
|
<Badge variant="neutral">AI Assistant</Badge>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|||||||
@@ -0,0 +1,148 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
// NOTE: This was shared by Kapa team with minor modifications.
|
||||||
|
|
||||||
|
import { useEffect, useState, useCallback } from "react"
|
||||||
|
import { useIsBrowser } from "../../providers"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper to execute a Promise with a timeout
|
||||||
|
*/
|
||||||
|
export async function executeWithTimeout<T>(
|
||||||
|
promise: Promise<T>,
|
||||||
|
timeout: number
|
||||||
|
): Promise<T> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const timer = setTimeout(() => {
|
||||||
|
reject(new Error("Promise timed out."))
|
||||||
|
}, timeout)
|
||||||
|
|
||||||
|
promise
|
||||||
|
.then((result) => {
|
||||||
|
clearTimeout(timer)
|
||||||
|
resolve(result)
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
clearTimeout(timer)
|
||||||
|
reject(error)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface Window {
|
||||||
|
grecaptcha: {
|
||||||
|
enterprise: {
|
||||||
|
execute: (id: string, action: { action: string }) => Promise<string>
|
||||||
|
ready: (callback: () => void) => void
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const RECAPTCHA_SCRIPT_ID = "kapa-recaptcha-script"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recaptcha action types to classify recaptcha assessments.
|
||||||
|
* IMPORTANT: Make sure these match the ones on the widget-proxy
|
||||||
|
*/
|
||||||
|
export enum RecaptchaAction {
|
||||||
|
AskAi = "ask_ai", // for /chat (/query) routes
|
||||||
|
FeedbackSubmit = "feedback_submit", // for /feedback routes
|
||||||
|
Search = "search", // for /search routes
|
||||||
|
}
|
||||||
|
|
||||||
|
type UseRecaptchaProps = {
|
||||||
|
siteKey: string
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This hook loads the reCAPTCHA SDK and exposes the "grecaptcha.execute" function
|
||||||
|
* which returns a recpatcha token. The token must then be validated on the backend.
|
||||||
|
* We use a reCAPTCHA Enterprise Score-based key, which is returning a score when
|
||||||
|
* calling the reCAPTCHA Enterprise API with the returned token from the `execute`
|
||||||
|
* call. The score indicates the probability of the request being made by a human.
|
||||||
|
* @param siteKey the reCAPTCHA (enterprise) site key
|
||||||
|
* @param loadScript boolean flag to load the reCAPTCHA script
|
||||||
|
*/
|
||||||
|
export const useRecaptcha = ({ siteKey }: UseRecaptchaProps) => {
|
||||||
|
const [isScriptLoaded, setIsScriptLoaded] = useState(false)
|
||||||
|
// The recaptcha execute function is not immediately
|
||||||
|
// ready so we need to wait until we can call it.
|
||||||
|
const [isExecuteReady, setIsExecuteReady] = useState(false)
|
||||||
|
const { isBrowser } = useIsBrowser()
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isBrowser) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (document.getElementById(RECAPTCHA_SCRIPT_ID)) {
|
||||||
|
setIsScriptLoaded(true)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const script = document.createElement("script")
|
||||||
|
script.id = RECAPTCHA_SCRIPT_ID
|
||||||
|
script.src = `https://www.google.com/recaptcha/enterprise.js?render=${siteKey}`
|
||||||
|
script.async = true
|
||||||
|
script.defer = true
|
||||||
|
|
||||||
|
const handleLoad = () => {
|
||||||
|
setIsScriptLoaded(true)
|
||||||
|
}
|
||||||
|
const handleError = (event: Event) => {
|
||||||
|
console.error("Failed to load reCAPTCHA Enterprise script", event)
|
||||||
|
}
|
||||||
|
|
||||||
|
script.addEventListener("load", handleLoad)
|
||||||
|
script.addEventListener("error", handleError)
|
||||||
|
|
||||||
|
document.head.appendChild(script)
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
if (script) {
|
||||||
|
script.removeEventListener("load", handleLoad)
|
||||||
|
script.removeEventListener("error", handleError)
|
||||||
|
document.head.removeChild(script)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [siteKey, isBrowser])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isScriptLoaded && window.grecaptcha) {
|
||||||
|
try {
|
||||||
|
window.grecaptcha.enterprise.ready(() => {
|
||||||
|
setIsExecuteReady(true)
|
||||||
|
})
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error during reCAPTCHA ready initialization:", error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [isScriptLoaded])
|
||||||
|
|
||||||
|
const execute = useCallback(
|
||||||
|
async (actionName: RecaptchaAction): Promise<string> => {
|
||||||
|
if (!isExecuteReady) {
|
||||||
|
console.error("reCAPTCHA is not ready")
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const token = await executeWithTimeout(
|
||||||
|
window.grecaptcha.enterprise.execute(siteKey, {
|
||||||
|
action: actionName,
|
||||||
|
}),
|
||||||
|
4000
|
||||||
|
)
|
||||||
|
return token
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error obtaining reCAPTCHA token:", error)
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[isExecuteReady, siteKey]
|
||||||
|
)
|
||||||
|
|
||||||
|
return { execute }
|
||||||
|
}
|
||||||
@@ -3,8 +3,7 @@
|
|||||||
import React, { createContext, useContext } from "react"
|
import React, { createContext, useContext } from "react"
|
||||||
import { useAnalytics } from "@/providers"
|
import { useAnalytics } from "@/providers"
|
||||||
import { AiAssistant } from "@/components"
|
import { AiAssistant } from "@/components"
|
||||||
// @ts-expect-error can't install the types package because it doesn't support React v19
|
import { RecaptchaAction, useRecaptcha } from "../../hooks/use-recaptcha"
|
||||||
import ReCAPTCHA from "react-google-recaptcha"
|
|
||||||
|
|
||||||
export type AiAssistantFeedbackType = "upvote" | "downvote"
|
export type AiAssistantFeedbackType = "upvote" | "downvote"
|
||||||
|
|
||||||
@@ -35,18 +34,13 @@ export const AiAssistantProvider = ({
|
|||||||
children,
|
children,
|
||||||
}: AiAssistantProviderProps) => {
|
}: AiAssistantProviderProps) => {
|
||||||
const { analytics } = useAnalytics()
|
const { analytics } = useAnalytics()
|
||||||
const recaptchaRef = React.createRef<ReCAPTCHA>()
|
const { execute: getReCaptchaToken } = useRecaptcha({
|
||||||
|
siteKey: recaptchaSiteKey,
|
||||||
const getReCaptchaToken = async () => {
|
})
|
||||||
if (recaptchaRef?.current) {
|
|
||||||
const recaptchaToken = await recaptchaRef.current.executeAsync()
|
|
||||||
return recaptchaToken || ""
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
const sendRequest = async (
|
const sendRequest = async (
|
||||||
apiPath: string,
|
apiPath: string,
|
||||||
|
action: RecaptchaAction,
|
||||||
method = "GET",
|
method = "GET",
|
||||||
headers?: HeadersInit,
|
headers?: HeadersInit,
|
||||||
body?: BodyInit
|
body?: BodyInit
|
||||||
@@ -54,7 +48,7 @@ export const AiAssistantProvider = ({
|
|||||||
return await fetch(`${apiUrl}${apiPath}`, {
|
return await fetch(`${apiUrl}${apiPath}`, {
|
||||||
method,
|
method,
|
||||||
headers: {
|
headers: {
|
||||||
"X-RECAPTCHA-TOKEN": await getReCaptchaToken(),
|
"X-RECAPTCHA-ENTERPRISE-TOKEN": await getReCaptchaToken(action),
|
||||||
"X-WEBSITE-ID": websiteId,
|
"X-WEBSITE-ID": websiteId,
|
||||||
...headers,
|
...headers,
|
||||||
},
|
},
|
||||||
@@ -67,7 +61,8 @@ export const AiAssistantProvider = ({
|
|||||||
return await sendRequest(
|
return await sendRequest(
|
||||||
threadId
|
threadId
|
||||||
? `/query/v1/thread/${threadId}/stream?query=${questionParam}`
|
? `/query/v1/thread/${threadId}/stream?query=${questionParam}`
|
||||||
: `/query/v1/stream?query=${questionParam}`
|
: `/query/v1/stream?query=${questionParam}`,
|
||||||
|
RecaptchaAction.AskAi
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -77,6 +72,7 @@ export const AiAssistantProvider = ({
|
|||||||
) => {
|
) => {
|
||||||
return await sendRequest(
|
return await sendRequest(
|
||||||
`/query/v1/question-answer/${questionId}/feedback`,
|
`/query/v1/question-answer/${questionId}/feedback`,
|
||||||
|
RecaptchaAction.FeedbackSubmit,
|
||||||
"POST",
|
"POST",
|
||||||
{
|
{
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
@@ -99,17 +95,6 @@ export const AiAssistantProvider = ({
|
|||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
<AiAssistant />
|
<AiAssistant />
|
||||||
<ReCAPTCHA
|
|
||||||
ref={recaptchaRef}
|
|
||||||
size="invisible"
|
|
||||||
sitekey={recaptchaSiteKey}
|
|
||||||
onErrored={() =>
|
|
||||||
console.error(
|
|
||||||
"ReCAPTCHA token not yet configured. Please reach out to the kapa team at founders@kapa.ai to complete the setup."
|
|
||||||
)
|
|
||||||
}
|
|
||||||
className="grecaptcha-badge"
|
|
||||||
/>
|
|
||||||
</AiAssistantContext.Provider>
|
</AiAssistantContext.Provider>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-36
@@ -8423,7 +8423,6 @@ __metadata:
|
|||||||
prism-react-renderer: 2.4.0
|
prism-react-renderer: 2.4.0
|
||||||
react: rc
|
react: rc
|
||||||
react-dom: rc
|
react-dom: rc
|
||||||
react-google-recaptcha: ^3.1.0
|
|
||||||
react-instantsearch: ^7.13.6
|
react-instantsearch: ^7.13.6
|
||||||
react-markdown: ^9.0.1
|
react-markdown: ^9.0.1
|
||||||
react-medium-image-zoom: ^5.2.10
|
react-medium-image-zoom: ^5.2.10
|
||||||
@@ -10478,15 +10477,6 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"hoist-non-react-statics@npm:^3.3.0":
|
|
||||||
version: 3.3.2
|
|
||||||
resolution: "hoist-non-react-statics@npm:3.3.2"
|
|
||||||
dependencies:
|
|
||||||
react-is: ^16.7.0
|
|
||||||
checksum: fe0889169e845d738b59b64badf5e55fa3cf20454f9203d1eb088df322d49d4318df774828e789898dcb280e8a5521bb59b3203385662ca5e9218a6ca5820e74
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"htm@npm:^3.0.0":
|
"htm@npm:^3.0.0":
|
||||||
version: 3.1.1
|
version: 3.1.1
|
||||||
resolution: "htm@npm:3.1.1"
|
resolution: "htm@npm:3.1.1"
|
||||||
@@ -13927,7 +13917,7 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"prop-types@npm:^15.5.0, prop-types@npm:^15.6.2, prop-types@npm:^15.8.1":
|
"prop-types@npm:^15.6.2, prop-types@npm:^15.8.1":
|
||||||
version: 15.8.1
|
version: 15.8.1
|
||||||
resolution: "prop-types@npm:15.8.1"
|
resolution: "prop-types@npm:15.8.1"
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -14056,18 +14046,6 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"react-async-script@npm:^1.2.0":
|
|
||||||
version: 1.2.0
|
|
||||||
resolution: "react-async-script@npm:1.2.0"
|
|
||||||
dependencies:
|
|
||||||
hoist-non-react-statics: ^3.3.0
|
|
||||||
prop-types: ^15.5.0
|
|
||||||
peerDependencies:
|
|
||||||
react: ">=16.4.1"
|
|
||||||
checksum: 89450912110c380abc08258ce17d2fb18d31d6b7179a74f6bc504c0761a4ca271edb671e402fa8e5ea4250b5c17fa953af80a9f1c4ebb26c9e81caee8476c903
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"react-currency-input-field@npm:^3.6.11":
|
"react-currency-input-field@npm:^3.6.11":
|
||||||
version: 3.8.0
|
version: 3.8.0
|
||||||
resolution: "react-currency-input-field@npm:3.8.0"
|
resolution: "react-currency-input-field@npm:3.8.0"
|
||||||
@@ -14116,18 +14094,6 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"react-google-recaptcha@npm:^3.1.0":
|
|
||||||
version: 3.1.0
|
|
||||||
resolution: "react-google-recaptcha@npm:3.1.0"
|
|
||||||
dependencies:
|
|
||||||
prop-types: ^15.5.0
|
|
||||||
react-async-script: ^1.2.0
|
|
||||||
peerDependencies:
|
|
||||||
react: ">=16.4.1"
|
|
||||||
checksum: 5ecaa6b88f238defd939012cb2671b4cbda59fe03f059158994b8c5215db482412e905eae6a67d23cef220d77cfe0430ab91ce7849d476515cda72f3a8a0a746
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"react-instantsearch-core@npm:7.13.6":
|
"react-instantsearch-core@npm:7.13.6":
|
||||||
version: 7.13.6
|
version: 7.13.6
|
||||||
resolution: "react-instantsearch-core@npm:7.13.6"
|
resolution: "react-instantsearch-core@npm:7.13.6"
|
||||||
@@ -14172,7 +14138,7 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"react-is@npm:^16.13.1, react-is@npm:^16.7.0":
|
"react-is@npm:^16.13.1":
|
||||||
version: 16.13.1
|
version: 16.13.1
|
||||||
resolution: "react-is@npm:16.13.1"
|
resolution: "react-is@npm:16.13.1"
|
||||||
checksum: 33977da7a5f1a287936a0c85639fec6ca74f4f15ef1e59a6bc20338fc73dc69555381e211f7a3529b8150a1f71e4225525b41b60b52965bda53ce7d47377ada1
|
checksum: 33977da7a5f1a287936a0c85639fec6ca74f4f15ef1e59a6bc20338fc73dc69555381e211f7a3529b8150a1f71e4225525b41b60b52965bda53ce7d47377ada1
|
||||||
|
|||||||
Reference in New Issue
Block a user