docs: open AI assistant based on search query (#13063)

* docs: open AI assistant based on search query

* use URLSearchParams

* fix vale error
This commit is contained in:
Shahed Nasser authored and GitHub committed 2025-07-28 12:59:09 +03:00
1 parent ea9ed62ee0
commit 7a07319a69
5 files changed
+162 -88

No files matched your search

@@ -1897,7 +1897,7 @@ If your use case is complex and these rules are not enough, you can create a new
Medusa v1 has gift card features out-of-the-box. Medusa v1 has gift card features out-of-the-box.
In Medusa v2, gift card features are now only available to [Medusa Cloud](https://medusajs.com/cloud/) users. In Medusa v2, gift card features are now only available to [Cloud](https://medusajs.com/cloud/) users.
--- ---
@@ -1,7 +1,7 @@
import React, { useEffect, useRef } from "react" import React, { useEffect, useMemo, useRef } from "react"
import clsx from "clsx" import clsx from "clsx"
import { ArrowUpCircleSolid } from "@medusajs/icons" import { ArrowUpCircleSolid } from "@medusajs/icons"
import { useAiAssistant } from "../../../../providers" import { useAiAssistant, useIsBrowser } from "../../../../providers"
import { useChat } from "@kapaai/react-sdk" import { useChat } from "@kapaai/react-sdk"
import { useAiAssistantChatNavigation } from "../../../../hooks" import { useAiAssistantChatNavigation } from "../../../../hooks"
@@ -12,8 +12,20 @@ type AiAssistantChatWindowInputProps = {
export const AiAssistantChatWindowInput = ({ export const AiAssistantChatWindowInput = ({
chatWindowRef, chatWindowRef,
}: AiAssistantChatWindowInputProps) => { }: AiAssistantChatWindowInputProps) => {
const { chatOpened, inputRef, loading } = useAiAssistant() const { chatOpened, inputRef, loading, setChatOpened } = useAiAssistant()
const { submitQuery, conversation } = useChat() const { submitQuery, conversation } = useChat()
const { isBrowser } = useIsBrowser()
const { searchQuery, searchQueryType } = useMemo(() => {
if (!isBrowser) {
return {}
}
const searchParams = new URLSearchParams(location.search)
return {
searchQuery: searchParams.get("query"),
searchQueryType: searchParams.get("queryType"),
}
}, [isBrowser])
const [question, setQuestion] = React.useState("") const [question, setQuestion] = React.useState("")
const formRef = useRef<HTMLFormElement | null>(null) const formRef = useRef<HTMLFormElement | null>(null)
@@ -97,6 +109,21 @@ export const AiAssistantChatWindowInput = ({
question, question,
}) })
useEffect(() => {
if (searchQueryType === "submit") {
onSubmit()
}
}, [searchQueryType])
useEffect(() => {
if (!searchQuery) {
return
}
setQuestion(searchQuery)
setChatOpened(true)
}, [searchQuery])
return ( return (
<div <div
className={clsx( className={clsx(
@@ -1,9 +1,18 @@
import React, { useState } from "react" import React, { useState } from "react"
import clsx from "clsx" import clsx from "clsx"
import { Badge, Button, Link, type ButtonProps } from "@/components" import { Badge, Button, Link, type ButtonProps } from "@/components"
import { ThumbDown, ThumbUp } from "@medusajs/icons" import {
import { AiAssistantThreadItem as AiAssistantThreadItemType } from "../../../../providers" ThumbDown,
ThumbUp,
Link as LinkIcon,
CheckCircle,
} from "@medusajs/icons"
import {
AiAssistantThreadItem as AiAssistantThreadItemType,
useSiteConfig,
} from "../../../../providers"
import { Reaction, useChat } from "@kapaai/react-sdk" import { Reaction, useChat } from "@kapaai/react-sdk"
import { useCopy } from "../../../../hooks"
export type AiAssistantThreadItemActionsProps = { export type AiAssistantThreadItemActionsProps = {
item: AiAssistantThreadItemType item: AiAssistantThreadItemType
@@ -14,6 +23,12 @@ export const AiAssistantThreadItemActions = ({
}: AiAssistantThreadItemActionsProps) => { }: AiAssistantThreadItemActionsProps) => {
const [feedback, setFeedback] = useState<Reaction | null>(null) const [feedback, setFeedback] = useState<Reaction | null>(null)
const { addFeedback } = useChat() const { addFeedback } = useChat()
const {
config: { baseUrl },
} = useSiteConfig()
const { handleCopy, isCopied } = useCopy(
`${baseUrl}?query=${encodeURI(item.content)}`
)
const handleFeedback = async ( const handleFeedback = async (
reaction: Reaction, reaction: Reaction,
@@ -31,36 +46,59 @@ export const AiAssistantThreadItemActions = ({
} }
return ( return (
<div className={clsx("flex gap-docs_0.75 justify-between items-center")}> <div
{item.sources !== undefined && item.sources.length > 0 && ( className={clsx(
<div className="flex gap-[6px] items-center flex-wrap"> "flex gap-docs_0.75 items-center",
{item.sources.map((source) => ( item.type === "question" && "justify-end",
<Badge key={source.source_url} variant="neutral"> item.type === "answer" && "justify-between"
<Link href={source.source_url} className="!text-inherit"> )}
{source.title} >
</Link> {item.type === "question" && (
</Badge> <div className="flex gap-docs_0.25 items-center text-medusa-fg-muted">
))} <ActionButton onClick={handleCopy}>
{isCopied ? <CheckCircle /> : <LinkIcon />}
</ActionButton>
</div> </div>
)} )}
<div className="flex gap-docs_0.25 items-center text-medusa-fg-muted"> {item.type === "answer" && (
{(feedback === null || feedback === "upvote") && ( <>
<ActionButton {item.sources !== undefined && item.sources.length > 0 && (
onClick={async () => handleFeedback("upvote", item.question_id)} <div className="flex gap-[6px] items-center flex-wrap">
className={clsx(feedback === "upvote" && "!text-medusa-fg-muted")} {item.sources.map((source) => (
> <Badge key={source.source_url} variant="neutral">
<ThumbUp /> <Link href={source.source_url} className="!text-inherit">
</ActionButton> {source.title}
)} </Link>
{(feedback === null || feedback === "downvote") && ( </Badge>
<ActionButton ))}
onClick={async () => handleFeedback("downvote", item.question_id)} </div>
className={clsx(feedback === "downvote" && "!text-medusa-fg-muted")} )}
> <div className="flex gap-docs_0.25 items-center text-medusa-fg-muted">
<ThumbDown /> {(feedback === null || feedback === "upvote") && (
</ActionButton> <ActionButton
)} onClick={async () => handleFeedback("upvote", item.question_id)}
</div> className={clsx(
feedback === "upvote" && "!text-medusa-fg-muted"
)}
>
<ThumbUp />
</ActionButton>
)}
{(feedback === null || feedback === "downvote") && (
<ActionButton
onClick={async () =>
handleFeedback("downvote", item.question_id)
}
className={clsx(
feedback === "downvote" && "!text-medusa-fg-muted"
)}
>
<ThumbDown />
</ActionButton>
)}
</div>
</>
)}
</div> </div>
) )
} }
@@ -41,80 +41,89 @@ export const AiAssistantThreadItem = ({ item }: AiAssistantThreadItemProps) => {
<div <div
className={clsx( className={clsx(
"txt-small text-medusa-fg-base", "txt-small text-medusa-fg-base",
item.type === "question" && [ "flex flex-col gap-docs_0.75",
"rounded-docs_xl bg-medusa-tag-neutral-bg",
"px-docs_0.75 py-docs_0.5 max-w-full md:max-w-[400px]",
],
item.type !== "question" && "flex-1", item.type !== "question" && "flex-1",
item.type === "answer" && "text-pretty flex-1 max-w-[calc(100%-20px)]" item.type === "answer" && "text-pretty flex-1 max-w-[calc(100%-20px)]"
)} )}
> >
{item.type === "question" && ( <div
<MarkdownContent className={clsx(
className="[&>*:last-child]:mb-0" "flex flex-col gap-docs_0.75",
allowedElements={["br", "p", "code", "pre"]} item.type === "question" && [
unwrapDisallowed={true} "rounded-docs_xl bg-medusa-tag-neutral-bg",
components={{ "px-docs_0.75 py-docs_0.5 max-w-full md:max-w-[400px]",
...MDXComponents, ]
code: (props: CodeMdxProps) => { )}
return ( >
<CodeMdx {item.type === "question" && (
{...props}
noCopy
noReport
forceNoTitle
noAskAi
inlineCodeProps={{
...props.inlineCodeProps,
className: "!text-wrap !break-words",
variant: "grey-bg",
}}
collapsibleLines="11"
codeBlockProps={{
className: clsx(
"rounded-docs_lg p-[5px]",
props.className
),
wrapperClassName: "rounded-docs_lg",
innerClassName: "border rounded-docs_lg",
overrideColors: {
bg: "bg-medusa-contrast-bg-subtle",
innerBg: "bg-medusa-contrast-bg-subtle",
innerBorder: "border-medusa-contrast-border-bot",
},
}}
/>
)
},
}}
>
{item.content}
</MarkdownContent>
)}
{item.type === "answer" && (
<div className="flex flex-col gap-docs_0.75">
{showLoading && <DotsLoading />}
<MarkdownContent <MarkdownContent
className="[&>*:last-child]:mb-0" className="[&>*:last-child]:mb-0"
allowedElements={["br", "p", "code", "pre"]}
unwrapDisallowed={true}
components={{ components={{
...MDXComponents, ...MDXComponents,
code: (props: CodeMdxProps) => { code: (props: CodeMdxProps) => {
return ( return (
<CodeMdx <CodeMdx
{...props} {...props}
noCopy
noReport noReport
forceNoTitle
noAskAi noAskAi
wrapperClassName="mt-docs_1" inlineCodeProps={{
...props.inlineCodeProps,
className: "!text-wrap !break-words",
variant: "grey-bg",
}}
collapsibleLines="11"
codeBlockProps={{
className: clsx(
"rounded-docs_lg p-[5px]",
props.className
),
wrapperClassName: "rounded-docs_lg",
innerClassName: "border rounded-docs_lg",
overrideColors: {
bg: "bg-medusa-contrast-bg-subtle",
innerBg: "bg-medusa-contrast-bg-subtle",
innerBorder: "border-medusa-contrast-border-bot",
},
}}
/> />
) )
}, },
}} }}
disallowedElements={["h1", "h2", "h3", "h4", "h5", "h6"]}
> >
{item.content} {item.content}
</MarkdownContent> </MarkdownContent>
{item.question_id && <AiAssistantThreadItemActions item={item} />} )}
</div> {item.type === "answer" && (
<>
{showLoading && <DotsLoading />}
<MarkdownContent
className="[&>*:last-child]:mb-0"
components={{
...MDXComponents,
code: (props: CodeMdxProps) => {
return (
<CodeMdx
{...props}
noReport
noAskAi
wrapperClassName="mt-docs_1"
/>
)
},
}}
disallowedElements={["h1", "h2", "h3", "h4", "h5", "h6"]}
>
{item.content}
</MarkdownContent>
</>
)}
</div>
{(item.question_id || item.type === "question") && (
<AiAssistantThreadItemActions item={item} />
)} )}
{item.type === "error" && ( {item.type === "error" && (
<span className="text-medusa-fg-error">{item.content}</span> <span className="text-medusa-fg-error">{item.content}</span>
@@ -23,7 +23,7 @@ export const InlineCode = ({
<code <code
{...props} {...props}
className={clsx( className={clsx(
"text-medusa-tag-neutral-text border", "text-medusa-tag-neutral-text border whitespace-break-spaces",
"font-monospace text-code-label rounded-docs_sm py-0 px-[5px]", "font-monospace text-code-label rounded-docs_sm py-0 px-[5px]",
variant === "default" && [ variant === "default" && [
"bg-medusa-tag-neutral-bg group-hover:bg-medusa-tag-neutral-bg-hover", "bg-medusa-tag-neutral-bg group-hover:bg-medusa-tag-neutral-bg-hover",