docs: use Kapa React SDK (#12792)
This commit is contained in:
@@ -15,7 +15,5 @@ NEXT_PUBLIC_DOCS_V1_URL=
|
||||
NEXT_PUBLIC_API_V1_URL=
|
||||
ALGOLIA_WRITE_API_KEY=
|
||||
ANALYZE_BUNDLE=
|
||||
NEXT_PUBLIC_AI_ASSISTANT_URL=
|
||||
NEXT_PUBLIC_AI_WEBSITE_ID=
|
||||
NEXT_PUBLIC_AI_API_ASSISTANT_RECAPTCHA_SITE_KEY=
|
||||
NEXT_PUBLIC_INTEGRATION_ID=
|
||||
NEXT_PUBLIC_GA_ID=
|
||||
@@ -24,12 +24,8 @@ const Providers = ({ children }: ProvidersProps) => {
|
||||
<MainNavProvider>
|
||||
<SearchProvider>
|
||||
<AiAssistantProvider
|
||||
apiUrl={process.env.NEXT_PUBLIC_AI_ASSISTANT_URL || "temp"}
|
||||
websiteId={process.env.NEXT_PUBLIC_AI_WEBSITE_ID || "temp"}
|
||||
recaptchaSiteKey={
|
||||
process.env
|
||||
.NEXT_PUBLIC_AI_API_ASSISTANT_RECAPTCHA_SITE_KEY ||
|
||||
"temp"
|
||||
integrationId={
|
||||
process.env.NEXT_PUBLIC_INTEGRATION_ID || "temp"
|
||||
}
|
||||
chatType="popover"
|
||||
>
|
||||
|
||||
@@ -13,10 +13,8 @@ NEXT_PUBLIC_API_URL=
|
||||
NEXT_PUBLIC_DOCS_V1_URL=
|
||||
ALGOLIA_WRITE_API_KEY=
|
||||
ANALYZE_BUNDLE=
|
||||
NEXT_PUBLIC_AI_ASSISTANT_URL=
|
||||
NEXT_PUBLIC_AI_WEBSITE_ID=
|
||||
NEXT_PUBLIC_AI_API_ASSISTANT_RECAPTCHA_SITE_KEY=
|
||||
CLOUDINARY_CLOUD_NAME=
|
||||
NEXT_PUBLIC_BASE_PATH=
|
||||
NEXT_PUBLIC_GA_ID=
|
||||
NEXT_PUBLIC_PROD_BASE_URL=
|
||||
NEXT_PUBLIC_PROD_BASE_URL=
|
||||
NEXT_PUBLIC_INTEGRATION_ID=
|
||||
@@ -34,16 +34,8 @@ const Providers = ({ children, aiAssistantProps = {} }: ProvidersProps) => {
|
||||
<SearchProvider>
|
||||
<AiAssistantProvider
|
||||
{...aiAssistantProps}
|
||||
apiUrl={
|
||||
process.env.NEXT_PUBLIC_AI_ASSISTANT_URL || "temp"
|
||||
}
|
||||
websiteId={
|
||||
process.env.NEXT_PUBLIC_AI_WEBSITE_ID || "temp"
|
||||
}
|
||||
recaptchaSiteKey={
|
||||
process.env
|
||||
.NEXT_PUBLIC_AI_API_ASSISTANT_RECAPTCHA_SITE_KEY ||
|
||||
"temp"
|
||||
integrationId={
|
||||
process.env.NEXT_PUBLIC_INTEGRATION_ID || "temp"
|
||||
}
|
||||
>
|
||||
<HooksLoader
|
||||
|
||||
@@ -13,8 +13,6 @@ NEXT_PUBLIC_API_URL=
|
||||
NEXT_PUBLIC_USER_GUIDE_URL=
|
||||
ALGOLIA_WRITE_API_KEY=
|
||||
ANALYZE_BUNDLE=
|
||||
NEXT_PUBLIC_AI_ASSISTANT_URL=
|
||||
NEXT_PUBLIC_AI_WEBSITE_ID=
|
||||
NEXT_PUBLIC_AI_API_ASSISTANT_RECAPTCHA_SITE_KEY=
|
||||
CLOUDINARY_CLOUD_NAME=
|
||||
NEXT_PUBLIC_GA_ID=
|
||||
NEXT_PUBLIC_GA_ID=
|
||||
NEXT_PUBLIC_INTEGRATION_ID=
|
||||
@@ -37,17 +37,8 @@ const Providers = ({ children }: ProvidersProps) => {
|
||||
<MainNavProvider>
|
||||
<SearchProvider>
|
||||
<AiAssistantProvider
|
||||
apiUrl={
|
||||
process.env.NEXT_PUBLIC_AI_ASSISTANT_URL ||
|
||||
"temp"
|
||||
}
|
||||
websiteId={
|
||||
process.env.NEXT_PUBLIC_AI_WEBSITE_ID || "temp"
|
||||
}
|
||||
recaptchaSiteKey={
|
||||
// eslint-disable-next-line prettier/prettier, max-len
|
||||
process.env.NEXT_PUBLIC_AI_API_ASSISTANT_RECAPTCHA_SITE_KEY ||
|
||||
"temp"
|
||||
integrationId={
|
||||
process.env.NEXT_PUBLIC_INTEGRATION_ID || "temp"
|
||||
}
|
||||
>
|
||||
<HooksLoader
|
||||
|
||||
@@ -18,4 +18,5 @@ NEXT_PUBLIC_AI_ASSISTANT_URL=
|
||||
NEXT_PUBLIC_AI_WEBSITE_ID=
|
||||
NEXT_PUBLIC_AI_API_ASSISTANT_RECAPTCHA_SITE_KEY=
|
||||
CLOUDINARY_CLOUD_NAME=
|
||||
NEXT_PUBLIC_GA_ID=
|
||||
NEXT_PUBLIC_GA_ID=
|
||||
NEXT_PUBLIC_INTEGRATION_ID=
|
||||
@@ -33,16 +33,8 @@ const Providers = ({ children }: ProvidersProps) => {
|
||||
<MainNavProvider>
|
||||
<SearchProvider>
|
||||
<AiAssistantProvider
|
||||
apiUrl={
|
||||
process.env.NEXT_PUBLIC_AI_ASSISTANT_URL || "temp"
|
||||
}
|
||||
websiteId={
|
||||
process.env.NEXT_PUBLIC_AI_WEBSITE_ID || "temp"
|
||||
}
|
||||
recaptchaSiteKey={
|
||||
process.env
|
||||
.NEXT_PUBLIC_AI_API_ASSISTANT_RECAPTCHA_SITE_KEY ||
|
||||
"temp"
|
||||
integrationId={
|
||||
process.env.NEXT_PUBLIC_INTEGRATION_ID || "temp"
|
||||
}
|
||||
>
|
||||
<HooksLoader
|
||||
|
||||
@@ -6,7 +6,5 @@ NEXT_PUBLIC_API_ALGOLIA_INDEX_NAME=
|
||||
NEXT_PUBLIC_ALGOLIA_API_KEY=
|
||||
NEXT_PUBLIC_ALGOLIA_APP_ID=
|
||||
NEXT_PUBLIC_SEGMENT_API_KEY=
|
||||
NEXT_PUBLIC_AI_ASSISTANT_URL=
|
||||
NEXT_PUBLIC_AI_WEBSITE_ID=
|
||||
NEXT_PUBLIC_AI_API_ASSISTANT_RECAPTCHA_SITE_KEY=
|
||||
NEXT_PUBLIC_GA_ID=
|
||||
NEXT_PUBLIC_GA_ID=
|
||||
NEXT_PUBLIC_INTEGRATION_ID=
|
||||
@@ -25,11 +25,8 @@ const Providers = ({ children }: ProvidersProps) => {
|
||||
<MainNavProvider>
|
||||
<SearchProvider>
|
||||
<AiAssistantProvider
|
||||
apiUrl={process.env.NEXT_PUBLIC_AI_ASSISTANT_URL || "temp"}
|
||||
websiteId={process.env.NEXT_PUBLIC_AI_WEBSITE_ID || "temp"}
|
||||
recaptchaSiteKey={
|
||||
process.env
|
||||
.NEXT_PUBLIC_AI_API_ASSISTANT_RECAPTCHA_SITE_KEY || "temp"
|
||||
integrationId={
|
||||
process.env.NEXT_PUBLIC_INTEGRATION_ID || "temp"
|
||||
}
|
||||
>
|
||||
<TooltipProvider>{children}</TooltipProvider>
|
||||
|
||||
@@ -13,8 +13,6 @@ NEXT_PUBLIC_API_URL=
|
||||
NEXT_PUBLIC_CLOUD_URL=
|
||||
ALGOLIA_WRITE_API_KEY=
|
||||
ANALYZE_BUNDLE=
|
||||
NEXT_PUBLIC_AI_ASSISTANT_URL=
|
||||
NEXT_PUBLIC_AI_WEBSITE_ID=
|
||||
NEXT_PUBLIC_AI_API_ASSISTANT_RECAPTCHA_SITE_KEY=
|
||||
CLOUDINARY_CLOUD_NAME=
|
||||
NEXT_PUBLIC_GA_ID=
|
||||
NEXT_PUBLIC_GA_ID=
|
||||
NEXT_PUBLIC_INTEGRATION_ID=
|
||||
@@ -37,17 +37,8 @@ const Providers = ({ children }: ProvidersProps) => {
|
||||
<MainNavProvider>
|
||||
<SearchProvider>
|
||||
<AiAssistantProvider
|
||||
apiUrl={
|
||||
process.env.NEXT_PUBLIC_AI_ASSISTANT_URL ||
|
||||
"temp"
|
||||
}
|
||||
websiteId={
|
||||
process.env.NEXT_PUBLIC_AI_WEBSITE_ID || "temp"
|
||||
}
|
||||
recaptchaSiteKey={
|
||||
// eslint-disable-next-line prettier/prettier, max-len
|
||||
process.env.NEXT_PUBLIC_AI_API_ASSISTANT_RECAPTCHA_SITE_KEY ||
|
||||
"temp"
|
||||
integrationId={
|
||||
process.env.NEXT_PUBLIC_INTEGRATION_ID || "temp"
|
||||
}
|
||||
>
|
||||
<HooksLoader
|
||||
|
||||
@@ -57,6 +57,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@emotion/is-prop-valid": "^1.3.1",
|
||||
"@kapaai/react-sdk": "^0.1.4",
|
||||
"@medusajs/icons": "2.8.4",
|
||||
"@medusajs/ui": "4.0.14",
|
||||
"@next/third-parties": "15.3.1",
|
||||
|
||||
@@ -1,33 +1,33 @@
|
||||
import React, { useEffect, useRef } from "react"
|
||||
import clsx from "clsx"
|
||||
import { useAiAssistantChat } from "../../../../providers/AiAssistant/Chat"
|
||||
import { ArrowUpCircleSolid } from "@medusajs/icons"
|
||||
import { useAiAssistant } from "../../../../providers"
|
||||
import { useChat } from "@kapaai/react-sdk"
|
||||
import { useAiAssistantChatNavigation } from "../../../../hooks"
|
||||
|
||||
export const AiAssistantChatWindowInput = () => {
|
||||
const {
|
||||
inputRef,
|
||||
question,
|
||||
setQuestion,
|
||||
handleSubmit: submitQuestion,
|
||||
loading,
|
||||
getThreadItems,
|
||||
} = useAiAssistantChat()
|
||||
const { chatOpened } = useAiAssistant()
|
||||
type AiAssistantChatWindowInputProps = {
|
||||
chatWindowRef: React.RefObject<HTMLDivElement | null>
|
||||
}
|
||||
|
||||
export const AiAssistantChatWindowInput = ({
|
||||
chatWindowRef,
|
||||
}: AiAssistantChatWindowInputProps) => {
|
||||
const { chatOpened, inputRef, loading } = useAiAssistant()
|
||||
const { submitQuery, conversation } = useChat()
|
||||
const [question, setQuestion] = React.useState("")
|
||||
const formRef = useRef<HTMLFormElement | null>(null)
|
||||
|
||||
const onSubmit = (e?: React.FormEvent<HTMLFormElement>) => {
|
||||
e?.preventDefault()
|
||||
submitQuestion()
|
||||
submitQuery(question)
|
||||
setQuestion("")
|
||||
}
|
||||
|
||||
const handleKeyboardDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
|
||||
if (e.key === "ArrowUp" && !question) {
|
||||
const lastQuestion = getThreadItems()
|
||||
.reverse()
|
||||
.find((item) => item.type === "question")
|
||||
const lastQuestion = conversation.getLatest()?.question
|
||||
if (lastQuestion) {
|
||||
setQuestion(lastQuestion.content)
|
||||
setQuestion(lastQuestion)
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -87,6 +87,16 @@ export const AiAssistantChatWindowInput = () => {
|
||||
}
|
||||
}, [chatOpened, inputRef.current])
|
||||
|
||||
useAiAssistantChatNavigation({
|
||||
getChatWindowElm: () => chatWindowRef.current as HTMLElement | null,
|
||||
getInputElm: () => inputRef.current as HTMLTextAreaElement | null,
|
||||
focusInput: () =>
|
||||
inputRef.current?.focus({
|
||||
preventScroll: true,
|
||||
}),
|
||||
question,
|
||||
})
|
||||
|
||||
return (
|
||||
<div
|
||||
className={clsx(
|
||||
@@ -103,7 +113,7 @@ export const AiAssistantChatWindowInput = () => {
|
||||
"appearance-none text-base md:text-small placeholder:text-medusa-fg-muted",
|
||||
"text-medusa-fg-base max-h-[210px] overflow-auto resize-none bg-transparent",
|
||||
"focus:outline-none focus:ring-0 disabled:cursor-not-allowed max-h-[210px]",
|
||||
"disabled:!bg-transparent disabled:text-medusa-fg-disabled"
|
||||
"disabled:!bg-transparent disabled:text-medusa-fg-disabled disabled:placeholder:text-medusa-fg-disabled"
|
||||
)}
|
||||
value={question}
|
||||
onChange={(e) => setQuestion(e.target.value)}
|
||||
|
||||
@@ -1,31 +1,37 @@
|
||||
"use client"
|
||||
|
||||
import clsx from "clsx"
|
||||
import React, { useCallback, useEffect, useRef, useState } from "react"
|
||||
import React, {
|
||||
Fragment,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useRef,
|
||||
useState,
|
||||
} from "react"
|
||||
import { useAiAssistant, useIsBrowser } from "../../../providers"
|
||||
import { AiAssistantChatWindowHeader } from "./Header"
|
||||
import { useAiAssistantChat } from "../../../providers/AiAssistant/Chat"
|
||||
import { AiAssistantSuggestions } from "../Suggestions"
|
||||
import { AiAssistantThreadItem } from "../ThreadItem"
|
||||
import { AiAssistantChatWindowInput } from "./Input"
|
||||
import { useAiAssistantChatNavigation, useKeyboardShortcut } from "../../.."
|
||||
import { useKeyboardShortcut } from "../../.."
|
||||
import { AiAssistantChatWindowFooter } from "./Footer"
|
||||
import { useChat } from "@kapaai/react-sdk"
|
||||
|
||||
const DEFAULT_HEIGHT = "calc(100% - 8px)"
|
||||
|
||||
export const AiAssistantChatWindow = () => {
|
||||
const { chatOpened, setChatOpened, chatType: type } = useAiAssistant()
|
||||
const {
|
||||
chatOpened,
|
||||
setChatOpened,
|
||||
chatType: type,
|
||||
inputRef,
|
||||
contentRef,
|
||||
loading,
|
||||
} = useAiAssistant()
|
||||
const { conversation, error } = useChat()
|
||||
const [height, setHeight] = useState(DEFAULT_HEIGHT)
|
||||
const [showFade, setShowFade] = useState(false)
|
||||
const { isBrowser } = useIsBrowser()
|
||||
const {
|
||||
inputRef,
|
||||
thread,
|
||||
getThreadItems: getChatThreadItems,
|
||||
answer,
|
||||
loading,
|
||||
contentRef,
|
||||
} = useAiAssistantChat()
|
||||
const chatWindowRef = useRef<HTMLDivElement | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
@@ -39,21 +45,27 @@ export const AiAssistantChatWindow = () => {
|
||||
}, [chatOpened])
|
||||
|
||||
const getThreadItems = useCallback(() => {
|
||||
const sortedThread = getChatThreadItems()
|
||||
|
||||
return sortedThread.map((item, index) => (
|
||||
<AiAssistantThreadItem item={item} key={index} />
|
||||
return conversation.map((item, index) => (
|
||||
<Fragment key={index}>
|
||||
<AiAssistantThreadItem
|
||||
item={{
|
||||
type: "question",
|
||||
content: item.question,
|
||||
sources: item.sources,
|
||||
question_id: item.id,
|
||||
}}
|
||||
/>
|
||||
<AiAssistantThreadItem
|
||||
item={{
|
||||
type: "answer",
|
||||
content: item.answer,
|
||||
sources: item.sources,
|
||||
question_id: item.id,
|
||||
}}
|
||||
/>
|
||||
</Fragment>
|
||||
))
|
||||
}, [getChatThreadItems])
|
||||
|
||||
useAiAssistantChatNavigation({
|
||||
getChatWindowElm: () => chatWindowRef.current as HTMLElement | null,
|
||||
getInputElm: () => inputRef.current as HTMLTextAreaElement | null,
|
||||
focusInput: () =>
|
||||
inputRef.current?.focus({
|
||||
preventScroll: true,
|
||||
}),
|
||||
})
|
||||
}, [conversation])
|
||||
|
||||
useKeyboardShortcut({
|
||||
metakey: false,
|
||||
@@ -75,7 +87,8 @@ export const AiAssistantChatWindow = () => {
|
||||
}
|
||||
setShowFade(
|
||||
!loading &&
|
||||
parentElm.offsetHeight + parentElm.scrollTop < parentElm.scrollHeight
|
||||
parentElm.offsetHeight + parentElm.scrollTop <
|
||||
parentElm.scrollHeight - 1
|
||||
)
|
||||
}
|
||||
|
||||
@@ -168,14 +181,13 @@ export const AiAssistantChatWindow = () => {
|
||||
)}
|
||||
>
|
||||
<div ref={contentRef}>
|
||||
{!thread.length && <AiAssistantSuggestions />}
|
||||
{!conversation.length && <AiAssistantSuggestions />}
|
||||
{getThreadItems()}
|
||||
{(answer.length || loading) && (
|
||||
{error?.length && (
|
||||
<AiAssistantThreadItem
|
||||
item={{
|
||||
type: "answer",
|
||||
content: answer,
|
||||
order: 0,
|
||||
type: "error",
|
||||
content: error,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
@@ -189,7 +201,7 @@ export const AiAssistantChatWindow = () => {
|
||||
)}
|
||||
></span>
|
||||
</div>
|
||||
<AiAssistantChatWindowInput />
|
||||
<AiAssistantChatWindowInput chatWindowRef={chatWindowRef} />
|
||||
<AiAssistantChatWindowFooter />
|
||||
</div>
|
||||
</>
|
||||
|
||||
@@ -1,41 +1,52 @@
|
||||
"use client"
|
||||
|
||||
import React, { useCallback } from "react"
|
||||
import React, { Fragment, useCallback, useState } from "react"
|
||||
import { Badge, Button, InputText, Kbd, Tooltip, Link } from "@/components"
|
||||
import { useSearch } from "@/providers"
|
||||
import { useAiAssistant, useSearch } from "@/providers"
|
||||
import { ArrowUturnLeft } from "@medusajs/icons"
|
||||
import clsx from "clsx"
|
||||
import { AiAssistantThreadItem } from "../ThreadItem"
|
||||
import { AiAssistantSuggestions } from "../Suggestions"
|
||||
import { useAiAssistantChat } from "../../../providers/AiAssistant/Chat"
|
||||
import { useSearchNavigation } from "../../.."
|
||||
import { useChat } from "@kapaai/react-sdk"
|
||||
|
||||
export const AiAssistantSearchWindow = () => {
|
||||
const {
|
||||
handleSubmit,
|
||||
getThreadItems: getChatThreadItems,
|
||||
question,
|
||||
setQuestion,
|
||||
inputRef,
|
||||
contentRef,
|
||||
loading,
|
||||
thread,
|
||||
answer,
|
||||
} = useAiAssistantChat()
|
||||
const { conversation, submitQuery, error } = useChat()
|
||||
const { inputRef, contentRef, loading } = useAiAssistant()
|
||||
const [question, setQuestion] = useState("")
|
||||
const { setCommand } = useSearch()
|
||||
|
||||
const getThreadItems = useCallback(() => {
|
||||
const sortedThread = getChatThreadItems()
|
||||
|
||||
return sortedThread.map((item, index) => (
|
||||
<AiAssistantThreadItem item={item} key={index} />
|
||||
return conversation.map((item, index) => (
|
||||
<Fragment key={index}>
|
||||
<AiAssistantThreadItem
|
||||
item={{
|
||||
type: "question",
|
||||
content: item.question,
|
||||
sources: item.sources,
|
||||
question_id: item.id,
|
||||
}}
|
||||
/>
|
||||
<AiAssistantThreadItem
|
||||
item={{
|
||||
type: "answer",
|
||||
content: item.answer,
|
||||
sources: item.sources,
|
||||
question_id: item.id,
|
||||
}}
|
||||
/>
|
||||
</Fragment>
|
||||
))
|
||||
}, [getChatThreadItems])
|
||||
}, [conversation])
|
||||
|
||||
useSearchNavigation({
|
||||
getInputElm: () => inputRef.current as HTMLInputElement | null,
|
||||
focusInput: () => inputRef.current?.focus(),
|
||||
handleSubmit,
|
||||
handleSubmit: () => {
|
||||
if (question.length > 0) {
|
||||
submitQuery(question)
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
return (
|
||||
@@ -101,14 +112,15 @@ export const AiAssistantSearchWindow = () => {
|
||||
</div>
|
||||
<div className="h-[calc(100%-95px)] lg:max-h-[calc(100%-140px)] lg:min-h-[calc(100%-140px)] overflow-auto">
|
||||
<div ref={contentRef}>
|
||||
{!thread.length && <AiAssistantSuggestions className="mx-docs_0.5" />}
|
||||
{!conversation.length && (
|
||||
<AiAssistantSuggestions className="mx-docs_0.5" />
|
||||
)}
|
||||
{getThreadItems()}
|
||||
{(answer.length || loading) && (
|
||||
{error?.length && (
|
||||
<AiAssistantThreadItem
|
||||
item={{
|
||||
type: "answer",
|
||||
content: answer,
|
||||
order: 0,
|
||||
type: "error",
|
||||
content: error,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
@@ -123,7 +135,7 @@ export const AiAssistantSearchWindow = () => {
|
||||
>
|
||||
<div className="flex items-center gap-docs_0.75">
|
||||
<div className="flex items-center gap-docs_0.5">
|
||||
{thread.length === 0 && (
|
||||
{conversation.length === 0 && (
|
||||
<>
|
||||
<span
|
||||
className={clsx(
|
||||
@@ -153,7 +165,7 @@ export const AiAssistantSearchWindow = () => {
|
||||
</span>
|
||||
</>
|
||||
)}
|
||||
{thread.length > 0 && (
|
||||
{conversation.length > 0 && (
|
||||
<span
|
||||
className={clsx("text-medusa-fg-muted", "text-compact-x-small")}
|
||||
>
|
||||
|
||||
@@ -1,56 +1,36 @@
|
||||
"use client"
|
||||
|
||||
import React, { useMemo } from "react"
|
||||
import React from "react"
|
||||
import { SearchSuggestionType } from "../../Search/Suggestions"
|
||||
import { useAiAssistant } from "../../../providers"
|
||||
import { SearchHitGroupName } from "../../Search/Hits/GroupName"
|
||||
import { SearchSuggestionItem } from "../../Search/Suggestions/Item"
|
||||
import { useAiAssistantChat } from "../../../providers/AiAssistant/Chat"
|
||||
import { useChat } from "@kapaai/react-sdk"
|
||||
|
||||
type AiAssistantSuggestionsProps = React.AllHTMLAttributes<HTMLDivElement>
|
||||
|
||||
export const AiAssistantSuggestions = (props: AiAssistantSuggestionsProps) => {
|
||||
const { version } = useAiAssistant()
|
||||
const { setQuestion, handleSubmit } = useAiAssistantChat()
|
||||
const suggestions: SearchSuggestionType[] = useMemo(() => {
|
||||
return version === "v2"
|
||||
? [
|
||||
{
|
||||
title: "FAQ",
|
||||
items: [
|
||||
"What is Medusa?",
|
||||
"How can I create a module?",
|
||||
"How can I create a data model?",
|
||||
"How do I create a workflow?",
|
||||
"How can I extend a data model in the Product Module?",
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Recipes",
|
||||
items: [
|
||||
"How do I build a marketplace with Medusa?",
|
||||
"How do I build digital products with Medusa?",
|
||||
"How do I build subscription-based purchases with Medusa?",
|
||||
"What other recipes are available in the Medusa documentation?",
|
||||
],
|
||||
},
|
||||
]
|
||||
: [
|
||||
{
|
||||
title: "FAQ",
|
||||
items: [
|
||||
"What is Medusa?",
|
||||
"How can I create an ecommerce store with Medusa?",
|
||||
"How can I build a marketplace with Medusa?",
|
||||
"How can I build subscription-based purchases with Medusa?",
|
||||
"How can I build digital products with Medusa?",
|
||||
"What can I build with Medusa?",
|
||||
"What is Medusa Admin?",
|
||||
"How do I configure the database in Medusa?",
|
||||
],
|
||||
},
|
||||
]
|
||||
}, [version])
|
||||
const { submitQuery } = useChat()
|
||||
const suggestions: SearchSuggestionType[] = [
|
||||
{
|
||||
title: "FAQ",
|
||||
items: [
|
||||
"What is Medusa?",
|
||||
"How can I create a module?",
|
||||
"How can I create a data model?",
|
||||
"How do I create a workflow?",
|
||||
"How can I extend a data model in the Product Module?",
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Recipes",
|
||||
items: [
|
||||
"How do I build a marketplace with Medusa?",
|
||||
"How do I build digital products with Medusa?",
|
||||
"How do I build subscription-based purchases with Medusa?",
|
||||
"What other recipes are available in the Medusa documentation?",
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
return (
|
||||
<div {...props}>
|
||||
@@ -60,8 +40,7 @@ export const AiAssistantSuggestions = (props: AiAssistantSuggestionsProps) => {
|
||||
{suggestion.items.map((item, itemIndex) => (
|
||||
<SearchSuggestionItem
|
||||
onClick={() => {
|
||||
setQuestion(item)
|
||||
handleSubmit(item)
|
||||
submitQuery(item)
|
||||
}}
|
||||
key={itemIndex}
|
||||
tabIndex={itemIndex}
|
||||
|
||||
@@ -2,33 +2,29 @@ import React, { useState } from "react"
|
||||
import clsx from "clsx"
|
||||
import { Badge, Button, Link, type ButtonProps } from "@/components"
|
||||
import { ThumbDown, ThumbUp } from "@medusajs/icons"
|
||||
import { AiAssistantFeedbackType, useAiAssistant } from "@/providers"
|
||||
import { AiAssistantThread } from "../../../../providers/AiAssistant/Chat"
|
||||
import { AiAssistantThreadItem as AiAssistantThreadItemType } from "../../../../providers"
|
||||
import { Reaction, useChat } from "@kapaai/react-sdk"
|
||||
|
||||
export type AiAssistantThreadItemActionsProps = {
|
||||
item: AiAssistantThread
|
||||
item: AiAssistantThreadItemType
|
||||
}
|
||||
|
||||
export const AiAssistantThreadItemActions = ({
|
||||
item,
|
||||
}: AiAssistantThreadItemActionsProps) => {
|
||||
const [feedback, setFeedback] = useState<AiAssistantFeedbackType | null>(null)
|
||||
const { sendFeedback } = useAiAssistant()
|
||||
const [feedback, setFeedback] = useState<Reaction | null>(null)
|
||||
const { addFeedback } = useChat()
|
||||
|
||||
const handleFeedback = async (
|
||||
reaction: AiAssistantFeedbackType,
|
||||
question_id?: string
|
||||
reaction: Reaction,
|
||||
question_id?: string | null
|
||||
) => {
|
||||
try {
|
||||
if (!question_id || feedback) {
|
||||
return
|
||||
}
|
||||
setFeedback(reaction)
|
||||
const response = await sendFeedback(question_id, reaction)
|
||||
|
||||
if (response.status !== 200) {
|
||||
console.error("Error sending feedback:", response.status)
|
||||
}
|
||||
addFeedback(question_id, reaction)
|
||||
} catch (error) {
|
||||
console.error("Error sending feedback:", error)
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import clsx from "clsx"
|
||||
import React from "react"
|
||||
import React, { useMemo } from "react"
|
||||
import {
|
||||
AiAssistantIcon,
|
||||
CodeMdx,
|
||||
@@ -9,13 +9,22 @@ import {
|
||||
MDXComponents,
|
||||
} from "@/components"
|
||||
import { AiAssistantThreadItemActions } from "./Actions"
|
||||
import { AiAssistantThread } from "../../../providers/AiAssistant/Chat"
|
||||
import { AiAssistantThreadItem as AiAssistantThreadItemType } from "../../../providers"
|
||||
import { useChat } from "@kapaai/react-sdk"
|
||||
|
||||
export type AiAssistantThreadItemProps = {
|
||||
item: AiAssistantThread
|
||||
item: AiAssistantThreadItemType
|
||||
}
|
||||
|
||||
export const AiAssistantThreadItem = ({ item }: AiAssistantThreadItemProps) => {
|
||||
const { error } = useChat()
|
||||
const showLoading = useMemo(() => {
|
||||
if (error?.length) {
|
||||
return false
|
||||
}
|
||||
|
||||
return !item.question_id && item.content.length === 0
|
||||
}, [item, error])
|
||||
return (
|
||||
<div
|
||||
className={clsx(
|
||||
@@ -84,15 +93,23 @@ export const AiAssistantThreadItem = ({ item }: AiAssistantThreadItemProps) => {
|
||||
)}
|
||||
{item.type === "answer" && (
|
||||
<div className="flex flex-col gap-docs_0.75">
|
||||
{!item.question_id && item.content.length === 0 && <DotsLoading />}
|
||||
{showLoading && <DotsLoading />}
|
||||
<MarkdownContent
|
||||
className="[&>*:last-child]:mb-0"
|
||||
components={{
|
||||
...MDXComponents,
|
||||
code: (props: CodeMdxProps) => {
|
||||
return <CodeMdx {...props} noReport noAskAi />
|
||||
return (
|
||||
<CodeMdx
|
||||
{...props}
|
||||
noReport
|
||||
noAskAi
|
||||
wrapperClassName="mt-docs_1"
|
||||
/>
|
||||
)
|
||||
},
|
||||
}}
|
||||
disallowedElements={["h1", "h2", "h3", "h4", "h5", "h6"]}
|
||||
>
|
||||
{item.content}
|
||||
</MarkdownContent>
|
||||
|
||||
@@ -9,7 +9,6 @@ import { useAiAssistant, useSearch, useSiteConfig } from "../../../providers"
|
||||
import { useKeyboardShortcut } from "../../../hooks"
|
||||
import Image from "next/image"
|
||||
|
||||
// const AI_ASSISTANT_ICON = "/images/ai-assistent-luminosity.png"
|
||||
const AI_ASSISTANT_ICON_ACTIVE = "/images/ai-assistent.png"
|
||||
|
||||
export const AiAssistantTriggerButton = () => {
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
|
||||
import React from "react"
|
||||
import { useAiAssistant, useSiteConfig } from "../../../../providers"
|
||||
import { useAiAssistantChat } from "../../../../providers/AiAssistant/Chat"
|
||||
import clsx from "clsx"
|
||||
import { Tooltip } from "../../../Tooltip"
|
||||
import Image from "next/image"
|
||||
import { useChat } from "@kapaai/react-sdk"
|
||||
|
||||
export type CodeBlockCopyActionProps = {
|
||||
source: string
|
||||
@@ -16,15 +16,15 @@ export const CodeBlockAskAiAction = ({
|
||||
source,
|
||||
inHeader,
|
||||
}: CodeBlockCopyActionProps) => {
|
||||
const { setChatOpened } = useAiAssistant()
|
||||
const { setQuestion, loading } = useAiAssistantChat()
|
||||
const { setChatOpened, loading } = useAiAssistant()
|
||||
const { submitQuery } = useChat()
|
||||
const { config } = useSiteConfig()
|
||||
|
||||
const handleClick = () => {
|
||||
if (loading) {
|
||||
return
|
||||
}
|
||||
setQuestion(`\`\`\`tsx\n${source.trim()}\n\`\`\`\n\nExplain the code above`)
|
||||
submitQuery(`\`\`\`tsx\n${source.trim()}\n\`\`\`\n\nExplain the code above`)
|
||||
setChatOpened(true)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
"use client"
|
||||
|
||||
import Link from "next/link"
|
||||
import React from "react"
|
||||
import React, { useMemo } from "react"
|
||||
import { MarkdownIcon } from "../../Icons/Markdown"
|
||||
import { useAiAssistant, useSiteConfig } from "../../../providers"
|
||||
import { usePathname } from "next/navigation"
|
||||
import { BroomSparkle } from "@medusajs/icons"
|
||||
import { useAiAssistantChat } from "../../../providers/AiAssistant/Chat"
|
||||
import { useChat } from "@kapaai/react-sdk"
|
||||
|
||||
export const ContentMenuActions = () => {
|
||||
const {
|
||||
@@ -14,14 +14,18 @@ export const ContentMenuActions = () => {
|
||||
} = useSiteConfig()
|
||||
const pathname = usePathname()
|
||||
const { setChatOpened } = useAiAssistant()
|
||||
const { setQuestion, loading } = useAiAssistantChat()
|
||||
const { isGeneratingAnswer, isPreparingAnswer, submitQuery } = useChat()
|
||||
const loading = useMemo(
|
||||
() => isGeneratingAnswer || isPreparingAnswer,
|
||||
[isGeneratingAnswer, isPreparingAnswer]
|
||||
)
|
||||
const pageUrl = `${baseUrl}${basePath}${pathname}`
|
||||
|
||||
const handleAiAssistantClick = () => {
|
||||
if (loading) {
|
||||
return
|
||||
}
|
||||
setQuestion(`Explain the page ${pageUrl}`)
|
||||
submitQuery(`Explain the page ${pageUrl}`)
|
||||
setChatOpened(true)
|
||||
}
|
||||
|
||||
|
||||
@@ -7,13 +7,13 @@ import {
|
||||
useKeyboardShortcut,
|
||||
type useKeyboardShortcutOptions,
|
||||
} from "../use-keyboard-shortcut"
|
||||
import { useAiAssistantChat } from "../../providers/AiAssistant/Chat"
|
||||
|
||||
export type UseAiAssistantChatNavigationProps = {
|
||||
getChatWindowElm: () => HTMLElement | null
|
||||
getInputElm: () => HTMLTextAreaElement | null
|
||||
focusInput: () => void
|
||||
keyboardProps?: Partial<useKeyboardShortcutOptions>
|
||||
question: string
|
||||
}
|
||||
|
||||
export const useAiAssistantChatNavigation = ({
|
||||
@@ -21,10 +21,10 @@ export const useAiAssistantChatNavigation = ({
|
||||
focusInput,
|
||||
keyboardProps,
|
||||
getChatWindowElm,
|
||||
question,
|
||||
}: UseAiAssistantChatNavigationProps) => {
|
||||
const shortcutKeys = useMemo(() => ["ArrowUp", "ArrowDown", "Enter"], [])
|
||||
const { chatOpened } = useAiAssistant()
|
||||
const { question } = useAiAssistantChat()
|
||||
|
||||
const handleKeyAction = (e: KeyboardEvent) => {
|
||||
const chatElm = getChatWindowElm()
|
||||
|
||||
@@ -1,159 +0,0 @@
|
||||
"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])
|
||||
|
||||
useEffect(() => {
|
||||
if (!isExecuteReady) {
|
||||
return
|
||||
}
|
||||
|
||||
const recaptchaElm = document.querySelector(".grecaptcha-badge")
|
||||
if (recaptchaElm?.parentElement) {
|
||||
recaptchaElm.parentElement.classList.add("absolute")
|
||||
}
|
||||
}, [isExecuteReady])
|
||||
|
||||
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 }
|
||||
}
|
||||
@@ -1,389 +0,0 @@
|
||||
"use client"
|
||||
|
||||
import React, {
|
||||
createContext,
|
||||
useCallback,
|
||||
useContext,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
} from "react"
|
||||
import { useAiAssistant } from ".."
|
||||
import useResizeObserver from "@react-hook/resize-observer"
|
||||
|
||||
export type AiAssistantChatContextType = {
|
||||
handleSubmit: (selectedQuestion?: string) => void
|
||||
getThreadItems: () => AiAssistantThread[]
|
||||
question: string
|
||||
setQuestion: React.Dispatch<React.SetStateAction<string>>
|
||||
inputRef: React.RefObject<HTMLInputElement | HTMLTextAreaElement | null>
|
||||
contentRef: React.RefObject<HTMLDivElement | null>
|
||||
loading: boolean
|
||||
thread: AiAssistantThread[]
|
||||
answer: string
|
||||
}
|
||||
|
||||
const AiAssistantChatContext = createContext<AiAssistantChatContextType | null>(
|
||||
null
|
||||
)
|
||||
|
||||
export type AiAssistantChunk = {
|
||||
stream_end: boolean
|
||||
} & (
|
||||
| {
|
||||
type: "relevant_sources"
|
||||
content: {
|
||||
relevant_sources: AiAssistantRelevantSources[]
|
||||
}
|
||||
}
|
||||
| {
|
||||
type: "partial_answer"
|
||||
content: AiAssistantPartialAnswer
|
||||
}
|
||||
| {
|
||||
type: "identifiers"
|
||||
content: AiAssistantIdentifier
|
||||
}
|
||||
| {
|
||||
type: "error"
|
||||
content: AiAssistantError
|
||||
}
|
||||
)
|
||||
|
||||
export type AiAssistantRelevantSources = {
|
||||
title: string
|
||||
source_url: string
|
||||
}
|
||||
|
||||
export type AiAssistantPartialAnswer = {
|
||||
text: string
|
||||
}
|
||||
|
||||
export type AiAssistantIdentifier = {
|
||||
thread_id: string
|
||||
question_answer_id: string
|
||||
}
|
||||
|
||||
export type AiAssistantError = {
|
||||
reason: string
|
||||
}
|
||||
|
||||
export type AiAssistantThread = {
|
||||
type: "question" | "answer" | "error"
|
||||
content: string
|
||||
question_id?: string
|
||||
sources?: AiAssistantRelevantSources[]
|
||||
// for some reason, items in the array get reordered
|
||||
// sometimes, so this is one way to avoid it
|
||||
order: number
|
||||
}
|
||||
|
||||
type AiAssistantChatProvider = {
|
||||
children: React.ReactNode
|
||||
}
|
||||
|
||||
export const AiAssistantChatProvider = ({
|
||||
children,
|
||||
}: AiAssistantChatProvider) => {
|
||||
const [question, setQuestion] = useState("")
|
||||
// this helps set the `order` field of the threadtype
|
||||
const [messagesCount, setMessagesCount] = useState(0)
|
||||
const [thread, setThread] = useState<AiAssistantThread[]>([])
|
||||
const [answer, setAnswer] = useState("")
|
||||
const [answerSources, setAnswerSources] = useState<
|
||||
AiAssistantRelevantSources[]
|
||||
>([])
|
||||
const [identifiers, setIdentifiers] = useState<AiAssistantIdentifier | null>(
|
||||
null
|
||||
)
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [preventAutoScroll, setPreventAutoScroll] = useState(false)
|
||||
const { getAnswer } = useAiAssistant()
|
||||
const inputRef = useRef<HTMLInputElement | HTMLTextAreaElement>(null)
|
||||
const contentRef = useRef<HTMLDivElement>(null)
|
||||
|
||||
const handleSubmit = (selectedQuestion?: string) => {
|
||||
if (!selectedQuestion?.length && !question.length) {
|
||||
return
|
||||
}
|
||||
setLoading(true)
|
||||
setPreventAutoScroll(false)
|
||||
setAnswer("")
|
||||
setThread((prevThread) => [
|
||||
...prevThread,
|
||||
{
|
||||
type: "question",
|
||||
content: selectedQuestion || question,
|
||||
order: getNewOrder(prevThread),
|
||||
},
|
||||
])
|
||||
setMessagesCount((prev) => prev + 1)
|
||||
}
|
||||
|
||||
const sortThread = (threadArr: AiAssistantThread[]) => {
|
||||
const sortedThread = [...threadArr]
|
||||
sortedThread.sort((itemA, itemB) => {
|
||||
if (itemA.order < itemB.order) {
|
||||
return -1
|
||||
}
|
||||
|
||||
return itemA.order < itemB.order ? 1 : 0
|
||||
})
|
||||
return sortedThread
|
||||
}
|
||||
|
||||
const getNewOrder = (prevThread: AiAssistantThread[]) => {
|
||||
const sortedThread = sortThread(prevThread)
|
||||
|
||||
return sortedThread.length === 0
|
||||
? messagesCount + 1
|
||||
: sortedThread[prevThread.length - 1].order + 1
|
||||
}
|
||||
|
||||
const setError = (logMessage?: string) => {
|
||||
if (logMessage?.length) {
|
||||
console.error(`[AI ERROR]: ${logMessage}`)
|
||||
}
|
||||
setThread((prevThread) => [
|
||||
...prevThread,
|
||||
{
|
||||
type: "error",
|
||||
content:
|
||||
"I'm sorry, but I'm having trouble connecting to my knowledge base. Please try again. If the issue keeps persisting, please consider reporting an issue.",
|
||||
order: getNewOrder(prevThread),
|
||||
},
|
||||
])
|
||||
setMessagesCount((prev) => prev + 1)
|
||||
setLoading(false)
|
||||
setQuestion("")
|
||||
setAnswer("")
|
||||
inputRef.current?.focus()
|
||||
}
|
||||
|
||||
const scrollToBottom = () => {
|
||||
if (preventAutoScroll) {
|
||||
return
|
||||
}
|
||||
const parent = contentRef.current?.parentElement as HTMLElement
|
||||
|
||||
parent.scrollTop = parent.scrollHeight
|
||||
}
|
||||
|
||||
const lastAnswerIndex = useMemo(() => {
|
||||
const index = thread.reverse().findIndex((item) => item.type === "answer")
|
||||
return index !== -1 ? index : 0
|
||||
}, [thread])
|
||||
|
||||
const process_stream = useCallback(async (response: Response) => {
|
||||
const reader = response.body?.getReader()
|
||||
if (!reader) {
|
||||
return
|
||||
}
|
||||
const decoder = new TextDecoder("utf-8")
|
||||
const delimiter = "\u241E"
|
||||
const delimiterBytes = new TextEncoder().encode(delimiter)
|
||||
let buffer = new Uint8Array()
|
||||
|
||||
const findDelimiterIndex = (arr: Uint8Array) => {
|
||||
for (let i = 0; i < arr.length - delimiterBytes.length + 1; i++) {
|
||||
let found = true
|
||||
for (let j = 0; j < delimiterBytes.length; j++) {
|
||||
if (arr[i + j] !== delimiterBytes[j]) {
|
||||
found = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if (found) {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
let result
|
||||
let loop = true
|
||||
while (loop) {
|
||||
result = await reader.read()
|
||||
if (result.done) {
|
||||
loop = false
|
||||
continue
|
||||
}
|
||||
buffer = new Uint8Array([...buffer, ...result.value])
|
||||
let delimiterIndex
|
||||
while ((delimiterIndex = findDelimiterIndex(buffer)) !== -1) {
|
||||
const chunkBytes = buffer.slice(0, delimiterIndex)
|
||||
const chunkText = decoder.decode(chunkBytes)
|
||||
buffer = buffer.slice(delimiterIndex + delimiterBytes.length)
|
||||
const chunk = JSON.parse(chunkText).chunk as AiAssistantChunk
|
||||
|
||||
switch (chunk.type) {
|
||||
case "partial_answer":
|
||||
setAnswer((prevAnswer) => prevAnswer + chunk.content.text)
|
||||
break
|
||||
case "identifiers":
|
||||
setIdentifiers(chunk.content)
|
||||
break
|
||||
case "error":
|
||||
setError(chunk.content.reason)
|
||||
loop = false
|
||||
return
|
||||
case "relevant_sources":
|
||||
setAnswerSources((prev) => [
|
||||
...prev,
|
||||
...chunk.content.relevant_sources,
|
||||
])
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setLoading(false)
|
||||
setQuestion("")
|
||||
}, [])
|
||||
|
||||
const fetchAnswer = useCallback(async () => {
|
||||
try {
|
||||
const response = await getAnswer(question, identifiers?.thread_id)
|
||||
|
||||
if (response.status === 200) {
|
||||
await process_stream(response)
|
||||
} else {
|
||||
const message = await response.text()
|
||||
setError(message)
|
||||
}
|
||||
} catch (error: any) {
|
||||
setError(JSON.stringify(error))
|
||||
}
|
||||
}, [question, identifiers, process_stream])
|
||||
|
||||
useEffect(() => {
|
||||
if (loading && !answer) {
|
||||
void fetchAnswer()
|
||||
}
|
||||
}, [loading, fetchAnswer])
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
loading ||
|
||||
!answer.length ||
|
||||
thread[lastAnswerIndex]?.content === answer
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
const uniqueAnswerSources = answerSources
|
||||
.filter(
|
||||
(source, index) =>
|
||||
answerSources.findIndex((s) => s.source_url === source.source_url) ===
|
||||
index
|
||||
)
|
||||
.map((source) => {
|
||||
const separatorIndex = source.title.indexOf("|")
|
||||
return {
|
||||
...source,
|
||||
title:
|
||||
separatorIndex !== -1
|
||||
? source.title.slice(0, separatorIndex)
|
||||
: source.title,
|
||||
}
|
||||
})
|
||||
setThread((prevThread) => [
|
||||
...prevThread,
|
||||
{
|
||||
type: "answer",
|
||||
content: answer,
|
||||
question_id: identifiers?.question_answer_id,
|
||||
order: getNewOrder(prevThread),
|
||||
sources:
|
||||
uniqueAnswerSources.length > 3
|
||||
? uniqueAnswerSources.slice(0, 3)
|
||||
: uniqueAnswerSources,
|
||||
},
|
||||
])
|
||||
setAnswer("")
|
||||
setAnswerSources([])
|
||||
setMessagesCount((prev) => prev + 1)
|
||||
inputRef.current?.focus()
|
||||
scrollToBottom()
|
||||
}, [loading, answer, thread, lastAnswerIndex, inputRef.current])
|
||||
|
||||
useResizeObserver(contentRef as React.RefObject<HTMLDivElement>, () => {
|
||||
if (!loading) {
|
||||
return
|
||||
}
|
||||
|
||||
scrollToBottom()
|
||||
})
|
||||
|
||||
const handleUserScroll = useCallback(() => {
|
||||
if (!question.length || preventAutoScroll) {
|
||||
return
|
||||
}
|
||||
|
||||
setPreventAutoScroll(true)
|
||||
}, [question, preventAutoScroll])
|
||||
|
||||
const handleUserScrollEnd = useCallback(() => {
|
||||
if (preventAutoScroll) {
|
||||
setPreventAutoScroll(false)
|
||||
}
|
||||
}, [preventAutoScroll])
|
||||
|
||||
useEffect(() => {
|
||||
if (!contentRef.current?.parentElement) {
|
||||
return
|
||||
}
|
||||
|
||||
contentRef.current.parentElement.addEventListener("wheel", handleUserScroll)
|
||||
contentRef.current.parentElement.addEventListener(
|
||||
"touchmove",
|
||||
handleUserScroll
|
||||
)
|
||||
|
||||
return () => {
|
||||
contentRef.current?.parentElement?.removeEventListener(
|
||||
"wheel",
|
||||
handleUserScroll
|
||||
)
|
||||
contentRef.current?.parentElement?.removeEventListener(
|
||||
"touchmove",
|
||||
handleUserScroll
|
||||
)
|
||||
}
|
||||
}, [contentRef.current, handleUserScroll])
|
||||
|
||||
const getThreadItems = useCallback(() => {
|
||||
return sortThread(thread)
|
||||
}, [thread])
|
||||
|
||||
return (
|
||||
<AiAssistantChatContext.Provider
|
||||
value={{
|
||||
handleSubmit,
|
||||
getThreadItems,
|
||||
question,
|
||||
setQuestion,
|
||||
inputRef,
|
||||
contentRef,
|
||||
loading,
|
||||
thread,
|
||||
answer,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</AiAssistantChatContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
export const useAiAssistantChat = () => {
|
||||
const context = useContext(AiAssistantChatContext)
|
||||
|
||||
if (!context) {
|
||||
throw new Error(
|
||||
"useAiAssistantChat must be used within a AiAssistantChatContext"
|
||||
)
|
||||
}
|
||||
|
||||
return context
|
||||
}
|
||||
@@ -1,147 +1,186 @@
|
||||
"use client"
|
||||
|
||||
import React, { createContext, useContext, useEffect, useState } from "react"
|
||||
import { useAnalytics, useSearch } from "@/providers"
|
||||
import { AiAssistantIcon, AiAssistantSearchWindow } from "@/components"
|
||||
import { RecaptchaAction, useRecaptcha } from "../../hooks/use-recaptcha"
|
||||
import { AiAssistantChatProvider } from "./Chat"
|
||||
|
||||
export type AiAssistantFeedbackType = "upvote" | "downvote"
|
||||
import { KapaProvider, useChat } from "@kapaai/react-sdk"
|
||||
import React, {
|
||||
createContext,
|
||||
useCallback,
|
||||
useContext,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
} from "react"
|
||||
import type { Source } from "@kapaai/react-sdk"
|
||||
import useResizeObserver from "@react-hook/resize-observer"
|
||||
import { AiAssistantSearchWindow } from "../../components"
|
||||
|
||||
export type AiAssistantChatType = "default" | "popover"
|
||||
|
||||
export type AiAssistantContextType = {
|
||||
getAnswer: (question: string, thread_id?: string) => Promise<Response>
|
||||
sendFeedback: (
|
||||
questionId: string,
|
||||
reaction: AiAssistantFeedbackType
|
||||
) => Promise<Response>
|
||||
version: "v1" | "v2"
|
||||
chatOpened: boolean
|
||||
setChatOpened: React.Dispatch<React.SetStateAction<boolean>>
|
||||
chatType: AiAssistantChatType
|
||||
inputRef: React.RefObject<HTMLInputElement | HTMLTextAreaElement | null>
|
||||
contentRef: React.RefObject<HTMLDivElement | null>
|
||||
loading: boolean
|
||||
}
|
||||
|
||||
export type AiAssistantThreadItem = {
|
||||
type: "question" | "answer" | "error"
|
||||
content: string
|
||||
question_id?: string | null
|
||||
sources?: Source[]
|
||||
}
|
||||
|
||||
const AiAssistantContext = createContext<AiAssistantContextType | null>(null)
|
||||
|
||||
export type AiAssistantProviderProps = {
|
||||
children?: React.ReactNode
|
||||
apiUrl: string
|
||||
recaptchaSiteKey: string
|
||||
websiteId: string
|
||||
version?: "v1" | "v2"
|
||||
type?: "search" | "chat"
|
||||
integrationId: string
|
||||
chatType?: AiAssistantChatType
|
||||
type?: "search" | "chat"
|
||||
}
|
||||
|
||||
export const AiAssistantProvider = ({
|
||||
apiUrl,
|
||||
recaptchaSiteKey,
|
||||
websiteId,
|
||||
version = "v2",
|
||||
type AiAssistantInnerProviderProps = Omit<
|
||||
AiAssistantProviderProps,
|
||||
"integrationId"
|
||||
> & {
|
||||
preventAutoScroll: boolean
|
||||
setPreventAutoScroll: React.Dispatch<React.SetStateAction<boolean>>
|
||||
setOnCompleteAction: React.Dispatch<React.SetStateAction<() => void>>
|
||||
}
|
||||
|
||||
const AiAssistantInnerProvider = ({
|
||||
children,
|
||||
type = "chat",
|
||||
chatType = "default",
|
||||
}: AiAssistantProviderProps) => {
|
||||
preventAutoScroll,
|
||||
setPreventAutoScroll,
|
||||
setOnCompleteAction,
|
||||
type,
|
||||
}: AiAssistantInnerProviderProps) => {
|
||||
const [chatOpened, setChatOpened] = useState(false)
|
||||
const { setCommands, setIsOpen, setCommand } = useSearch()
|
||||
const { analytics } = useAnalytics()
|
||||
const { execute: getReCaptchaToken } = useRecaptcha({
|
||||
siteKey: recaptchaSiteKey,
|
||||
const inputRef = useRef<HTMLInputElement | HTMLTextAreaElement>(null)
|
||||
const contentRef = useRef<HTMLDivElement>(null)
|
||||
const { isGeneratingAnswer, isPreparingAnswer } = useChat()
|
||||
const loading = useMemo(
|
||||
() => isGeneratingAnswer || isPreparingAnswer,
|
||||
[isGeneratingAnswer, isPreparingAnswer]
|
||||
)
|
||||
|
||||
const scrollToBottom = () => {
|
||||
if (preventAutoScroll) {
|
||||
return
|
||||
}
|
||||
const parent = contentRef.current?.parentElement as HTMLElement
|
||||
|
||||
parent.scrollTop = parent.scrollHeight
|
||||
}
|
||||
|
||||
useResizeObserver(contentRef as React.RefObject<HTMLDivElement>, () => {
|
||||
if (!loading) {
|
||||
return
|
||||
}
|
||||
|
||||
scrollToBottom()
|
||||
})
|
||||
|
||||
const sendRequest = async (
|
||||
apiPath: string,
|
||||
action: RecaptchaAction,
|
||||
method = "GET",
|
||||
headers?: HeadersInit,
|
||||
body?: BodyInit
|
||||
) => {
|
||||
return await fetch(`${apiUrl}${apiPath}`, {
|
||||
method,
|
||||
headers: {
|
||||
"X-RECAPTCHA-ENTERPRISE-TOKEN": await getReCaptchaToken(action),
|
||||
"X-WEBSITE-ID": websiteId,
|
||||
...headers,
|
||||
},
|
||||
body,
|
||||
})
|
||||
}
|
||||
const handleUserScroll = useCallback(() => {
|
||||
if (!loading || preventAutoScroll) {
|
||||
return
|
||||
}
|
||||
|
||||
const getAnswer = async (question: string, threadId?: string) => {
|
||||
const questionParam = encodeURI(question)
|
||||
return await sendRequest(
|
||||
threadId
|
||||
? `/query/v1/thread/${threadId}/stream?query=${questionParam}`
|
||||
: `/query/v1/stream?query=${questionParam}`,
|
||||
RecaptchaAction.AskAi
|
||||
)
|
||||
}
|
||||
|
||||
const sendFeedback = async (
|
||||
questionId: string,
|
||||
reaction: AiAssistantFeedbackType
|
||||
) => {
|
||||
return await sendRequest(
|
||||
`/query/v1/question-answer/${questionId}/feedback`,
|
||||
RecaptchaAction.FeedbackSubmit,
|
||||
"POST",
|
||||
{
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
JSON.stringify({
|
||||
question_id: questionId,
|
||||
reaction,
|
||||
user_identifier: analytics?.user().anonymousId() || "",
|
||||
})
|
||||
)
|
||||
}
|
||||
setPreventAutoScroll(true)
|
||||
}, [loading, preventAutoScroll])
|
||||
|
||||
useEffect(() => {
|
||||
setCommands((prevCommands) => {
|
||||
const newCommands = [...prevCommands]
|
||||
if (!contentRef.current?.parentElement) {
|
||||
return
|
||||
}
|
||||
|
||||
if (!newCommands.find((c) => c.name === "ai-assistant")) {
|
||||
newCommands.push({
|
||||
name: "ai-assistant",
|
||||
icon: <AiAssistantIcon />,
|
||||
title: "AI Assistant",
|
||||
badge: {
|
||||
variant: "blue",
|
||||
badgeType: "shaded",
|
||||
children: "Beta",
|
||||
},
|
||||
action: () => {
|
||||
setIsOpen(false)
|
||||
setChatOpened(true)
|
||||
setCommand(null)
|
||||
},
|
||||
})
|
||||
contentRef.current.parentElement.addEventListener(
|
||||
"wheel",
|
||||
handleUserScroll,
|
||||
{
|
||||
passive: true,
|
||||
}
|
||||
)
|
||||
contentRef.current.parentElement.addEventListener(
|
||||
"touchmove",
|
||||
handleUserScroll,
|
||||
{
|
||||
passive: true,
|
||||
}
|
||||
)
|
||||
|
||||
return newCommands
|
||||
return () => {
|
||||
contentRef.current?.parentElement?.removeEventListener(
|
||||
"wheel",
|
||||
handleUserScroll
|
||||
)
|
||||
contentRef.current?.parentElement?.removeEventListener(
|
||||
"touchmove",
|
||||
handleUserScroll
|
||||
)
|
||||
}
|
||||
}, [contentRef.current, handleUserScroll])
|
||||
|
||||
useEffect(() => {
|
||||
setOnCompleteAction(() => {
|
||||
scrollToBottom()
|
||||
inputRef.current?.focus({
|
||||
preventScroll: true,
|
||||
})
|
||||
})
|
||||
}, [])
|
||||
}, [scrollToBottom])
|
||||
|
||||
return (
|
||||
<AiAssistantContext.Provider
|
||||
value={{
|
||||
getAnswer,
|
||||
sendFeedback,
|
||||
version,
|
||||
chatOpened,
|
||||
setChatOpened,
|
||||
chatType,
|
||||
inputRef,
|
||||
contentRef,
|
||||
loading,
|
||||
}}
|
||||
>
|
||||
<AiAssistantChatProvider>
|
||||
{children}
|
||||
{type === "search" && <AiAssistantSearchWindow />}
|
||||
</AiAssistantChatProvider>
|
||||
{children}
|
||||
{type === "search" && <AiAssistantSearchWindow />}
|
||||
</AiAssistantContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
export const AiAssistantProvider = ({
|
||||
integrationId,
|
||||
...props
|
||||
}: AiAssistantProviderProps) => {
|
||||
const [preventAutoScroll, setPreventAutoScroll] = useState(false)
|
||||
const [onCompleteAction, setOnCompleteAction] = useState<() => void>(
|
||||
() => () => {}
|
||||
)
|
||||
|
||||
return (
|
||||
<KapaProvider
|
||||
integrationId={integrationId}
|
||||
callbacks={{
|
||||
askAI: {
|
||||
onAnswerGenerationCompleted: () => {
|
||||
onCompleteAction?.()
|
||||
},
|
||||
onQuerySubmit: () => setPreventAutoScroll(false),
|
||||
},
|
||||
}}
|
||||
>
|
||||
<AiAssistantInnerProvider
|
||||
{...props}
|
||||
preventAutoScroll={preventAutoScroll}
|
||||
setPreventAutoScroll={setPreventAutoScroll}
|
||||
setOnCompleteAction={setOnCompleteAction}
|
||||
/>
|
||||
</KapaProvider>
|
||||
)
|
||||
}
|
||||
|
||||
export const useAiAssistant = () => {
|
||||
const context = useContext(AiAssistantContext)
|
||||
|
||||
|
||||
120
www/yarn.lock
120
www/yarn.lock
@@ -518,6 +518,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@babel/runtime@npm:^7.17.9":
|
||||
version: 7.27.6
|
||||
resolution: "@babel/runtime@npm:7.27.6"
|
||||
checksum: 89726be83f356f511dcdb74d3ea4d873a5f0cf0017d4530cb53aa27380c01ca102d573eff8b8b77815e624b1f8c24e7f0311834ad4fb632c90a770fda00bd4c8
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@babel/runtime@npm:^7.22.5":
|
||||
version: 7.26.10
|
||||
resolution: "@babel/runtime@npm:7.26.10"
|
||||
@@ -1079,6 +1086,35 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@fingerprintjs/fingerprintjs-pro-react@npm:^2.6.3":
|
||||
version: 2.7.0
|
||||
resolution: "@fingerprintjs/fingerprintjs-pro-react@npm:2.7.0"
|
||||
dependencies:
|
||||
"@fingerprintjs/fingerprintjs-pro-spa": ^1.3.2
|
||||
fast-deep-equal: 3.1.3
|
||||
checksum: 8892e325885ba4d5a10ffc48764dc016dcff73c1fc356c9aff245fbdb48c17b5b99c74ad280017f6739d0f035e505ece0006ad74116fc947bcb7c6042591d6a5
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@fingerprintjs/fingerprintjs-pro-spa@npm:^1.3.2":
|
||||
version: 1.3.2
|
||||
resolution: "@fingerprintjs/fingerprintjs-pro-spa@npm:1.3.2"
|
||||
dependencies:
|
||||
"@fingerprintjs/fingerprintjs-pro": ^3.11.0
|
||||
tslib: ^2.7.0
|
||||
checksum: a53fb7be19fe04dcb9705bcbd790ff7a411864ab031044193292046cbd63ec1c80672ed67a5b5326eef0320af35f21853281294fca471148c25ceb1aca24b555
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@fingerprintjs/fingerprintjs-pro@npm:^3.11.0":
|
||||
version: 3.11.10
|
||||
resolution: "@fingerprintjs/fingerprintjs-pro@npm:3.11.10"
|
||||
dependencies:
|
||||
tslib: ^2.4.1
|
||||
checksum: 981cdeabaf63da38062ed4864193d13df44aa5bd2654135d75866d4f4d06e60cceb8711973d2d881820ebb05b5a05d7ee0ffba1b0990c2f13ae5b5e72e0a3b71
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@floating-ui/core@npm:^1.0.0":
|
||||
version: 1.6.0
|
||||
resolution: "@floating-ui/core@npm:1.6.0"
|
||||
@@ -1190,6 +1226,26 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@hcaptcha/loader@npm:^2.0.0":
|
||||
version: 2.0.0
|
||||
resolution: "@hcaptcha/loader@npm:2.0.0"
|
||||
checksum: c69c8e62ccf41cc5cce8f25721b9bd8284e201da608022476dc6d69afbd70512a6f272dd2bfe558c4b5fdbc0ee20fb5f062033c4d556e6d523ecb2448203298f
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@hcaptcha/react-hcaptcha@npm:^1.12.0":
|
||||
version: 1.12.0
|
||||
resolution: "@hcaptcha/react-hcaptcha@npm:1.12.0"
|
||||
dependencies:
|
||||
"@babel/runtime": ^7.17.9
|
||||
"@hcaptcha/loader": ^2.0.0
|
||||
peerDependencies:
|
||||
react: ">= 16.3.0"
|
||||
react-dom: ">= 16.3.0"
|
||||
checksum: 58fddf7cbbeb1c9d784a90a4ee76604efd8d0c968d1d256f72d112d95c8d68bbb1255dd4184148e7e43e38491c5614b3747f4d17393a17d320e28935da47f8e8
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@humanfs/core@npm:^0.19.0":
|
||||
version: 0.19.0
|
||||
resolution: "@humanfs/core@npm:0.19.0"
|
||||
@@ -1712,6 +1768,22 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@kapaai/react-sdk@npm:^0.1.4":
|
||||
version: 0.1.4
|
||||
resolution: "@kapaai/react-sdk@npm:0.1.4"
|
||||
dependencies:
|
||||
"@fingerprintjs/fingerprintjs-pro-react": ^2.6.3
|
||||
"@hcaptcha/react-hcaptcha": ^1.12.0
|
||||
"@tanstack/react-query": ^5.74.3
|
||||
js-cookie: ^3.0.5
|
||||
tldts: ^7.0.7
|
||||
peerDependencies:
|
||||
react: ">=17.0.0"
|
||||
react-dom: ">=17.0.0"
|
||||
checksum: 235c976c66a4caf031f30501e61dcad9429e302ca217acb7594af20161cd6891b760e8a345866db99ada18551edbb7e7cbfde80a95a74fc08992608c109e9d0c
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@lukeed/csprng@npm:^1.1.0":
|
||||
version: 1.1.0
|
||||
resolution: "@lukeed/csprng@npm:1.1.0"
|
||||
@@ -5458,6 +5530,24 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@tanstack/query-core@npm:5.80.10":
|
||||
version: 5.80.10
|
||||
resolution: "@tanstack/query-core@npm:5.80.10"
|
||||
checksum: 6391f4439f1f4676f5efeb07271e6b0b1418bd1192779f55cac537f0b35ec31c729eb43a30e0269534328c259c3ed6dd2f31555c42c68b6dd0c9ede410d46eb1
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@tanstack/react-query@npm:^5.74.3":
|
||||
version: 5.80.10
|
||||
resolution: "@tanstack/react-query@npm:5.80.10"
|
||||
dependencies:
|
||||
"@tanstack/query-core": 5.80.10
|
||||
peerDependencies:
|
||||
react: ^18 || ^19
|
||||
checksum: 1a759b264cc94b389f802f1183183a3fbe1669ec1bb354951cfab6b6cf175276885c5f5c9b3fc00340a87140fdff4b9cf0c688fb7af103d34fb3b209d33a0a95
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@tanstack/react-table@npm:8.20.5":
|
||||
version: 8.20.5
|
||||
resolution: "@tanstack/react-table@npm:8.20.5"
|
||||
@@ -8022,6 +8112,7 @@ __metadata:
|
||||
resolution: "docs-ui@workspace:packages/docs-ui"
|
||||
dependencies:
|
||||
"@emotion/is-prop-valid": ^1.3.1
|
||||
"@kapaai/react-sdk": ^0.1.4
|
||||
"@medusajs/icons": 2.8.4
|
||||
"@medusajs/ui": 4.0.14
|
||||
"@next/third-parties": 15.3.1
|
||||
@@ -9487,7 +9578,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"fast-deep-equal@npm:^3.1.1, fast-deep-equal@npm:^3.1.3":
|
||||
"fast-deep-equal@npm:3.1.3, fast-deep-equal@npm:^3.1.1, fast-deep-equal@npm:^3.1.3":
|
||||
version: 3.1.3
|
||||
resolution: "fast-deep-equal@npm:3.1.3"
|
||||
checksum: 40dedc862eb8992c54579c66d914635afbec43350afbbe991235fdcb4e3a8d5af1b23ae7e79bef7d4882d0ecee06c3197488026998fb19f72dc95acff1d1b1d0
|
||||
@@ -11291,6 +11382,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"js-cookie@npm:^3.0.5":
|
||||
version: 3.0.5
|
||||
resolution: "js-cookie@npm:3.0.5"
|
||||
checksum: 04a0e560407b4489daac3a63e231d35f4e86f78bff9d792011391b49c59f721b513411cd75714c418049c8dc9750b20fcddad1ca5a2ca616c3aca4874cce5b3a
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"js-tokens@npm:^3.0.0 || ^4.0.0, js-tokens@npm:^4.0.0":
|
||||
version: 4.0.0
|
||||
resolution: "js-tokens@npm:4.0.0"
|
||||
@@ -16319,6 +16417,24 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"tldts-core@npm:^7.0.9":
|
||||
version: 7.0.9
|
||||
resolution: "tldts-core@npm:7.0.9"
|
||||
checksum: ff83afc0123fb3cb60036535c8ee5c117393a6fd659d36a247d7b7dface48645cf0b1c89c5223a5d920b41a34a34a2a662086980914ff9db63be37b485626d00
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"tldts@npm:^7.0.7":
|
||||
version: 7.0.9
|
||||
resolution: "tldts@npm:7.0.9"
|
||||
dependencies:
|
||||
tldts-core: ^7.0.9
|
||||
bin:
|
||||
tldts: bin/cli.js
|
||||
checksum: 910d121c9c0f3f5a50fd19f1baa430430dac6fb9de389d11ea989526703f66a0f614c65b9b3f1144b3357ba35cedf204e654c88f71a2932409c11aa4401bdcff
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"to-fast-properties@npm:^2.0.0":
|
||||
version: 2.0.0
|
||||
resolution: "to-fast-properties@npm:2.0.0"
|
||||
@@ -16541,7 +16657,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"tslib@npm:^2.8.0":
|
||||
"tslib@npm:^2.7.0, tslib@npm:^2.8.0":
|
||||
version: 2.8.1
|
||||
resolution: "tslib@npm:2.8.1"
|
||||
checksum: 9c4759110a19c53f992d9aae23aac5ced636e99887b51b9e61def52611732872ff7668757d4e4c61f19691e36f4da981cd9485e869b4a7408d689f6bf1f14e62
|
||||
|
||||
Reference in New Issue
Block a user