docs: use Kapa React SDK (#12792)

This commit is contained in:
Shahed Nasser authored and GitHub committed 2025-06-20 18:24:14 +03:00
1 parent 77d0f00929
commit ab21ac57ca
27 files changed
+462 -875

No files matched your search

@@ -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)
}