docs: improve AI Assistant (#11208)
* initial implementation * integrate new ai assistant in other projects + ux improvements * fix chat window on mobile devices * fixes to mobile * allow pre * change shortcut to i * improved responsiveness * align version in navbar
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
export * from "./use-active-on-scroll"
|
||||
export * from "./use-ai-assistant-chat-navigation"
|
||||
export * from "./use-child-docs"
|
||||
export * from "./use-click-outside"
|
||||
export * from "./use-collapsible"
|
||||
|
||||
@@ -0,0 +1,141 @@
|
||||
"use client"
|
||||
|
||||
import { useCallback, useEffect, useMemo } from "react"
|
||||
import { useAiAssistant } from "@/providers"
|
||||
import { findNextSibling, findPrevSibling } from "@/utils"
|
||||
import {
|
||||
useKeyboardShortcut,
|
||||
type useKeyboardShortcutOptions,
|
||||
} from "../use-keyboard-shortcut"
|
||||
|
||||
export type UseAiAssistantChatNavigationProps = {
|
||||
getChatWindowElm: () => HTMLElement | null
|
||||
getInputElm: () => HTMLTextAreaElement | null
|
||||
focusInput: () => void
|
||||
keyboardProps?: Partial<useKeyboardShortcutOptions>
|
||||
}
|
||||
|
||||
export const useAiAssistantChatNavigation = ({
|
||||
getInputElm,
|
||||
focusInput,
|
||||
keyboardProps,
|
||||
getChatWindowElm,
|
||||
}: UseAiAssistantChatNavigationProps) => {
|
||||
const shortcutKeys = useMemo(() => ["ArrowUp", "ArrowDown", "Enter"], [])
|
||||
const { chatOpened } = useAiAssistant()
|
||||
|
||||
const handleKeyAction = (e: KeyboardEvent) => {
|
||||
const chatElm = getChatWindowElm()
|
||||
if (
|
||||
!chatOpened ||
|
||||
e.metaKey ||
|
||||
e.ctrlKey ||
|
||||
!chatElm?.contains(document.activeElement)
|
||||
) {
|
||||
return
|
||||
}
|
||||
e.preventDefault()
|
||||
const focusedItem = chatElm?.querySelector(":focus") as HTMLElement
|
||||
if (!focusedItem) {
|
||||
// focus the first data-hit
|
||||
const nextItem = chatElm?.querySelector("[data-hit]") as HTMLElement
|
||||
nextItem?.focus()
|
||||
return
|
||||
}
|
||||
|
||||
const isHit = focusedItem.hasAttribute("data-hit")
|
||||
const isInput = focusedItem.tagName.toLowerCase() === "textarea"
|
||||
|
||||
if (!isHit && !isInput) {
|
||||
// ignore if focused items aren't input/data-hit
|
||||
return
|
||||
}
|
||||
|
||||
const lowerPressedKey = e.key.toLowerCase()
|
||||
|
||||
if (lowerPressedKey === "enter") {
|
||||
if (isHit) {
|
||||
// trigger click event of the focused element
|
||||
focusedItem.click()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if (lowerPressedKey === "arrowdown") {
|
||||
// only hit items has action on arrow down
|
||||
if (isHit) {
|
||||
// find if there's a data-hit item before this one
|
||||
const beforeItem = findNextSibling(focusedItem, "[data-hit]")
|
||||
if (!beforeItem) {
|
||||
// focus the input
|
||||
focusInput()
|
||||
} else {
|
||||
// focus the previous item
|
||||
beforeItem.focus()
|
||||
}
|
||||
}
|
||||
} else if (lowerPressedKey === "arrowup") {
|
||||
// check if item is input or hit
|
||||
if (isInput) {
|
||||
// go to the first data-hit item
|
||||
const nextItem = chatElm?.querySelector(
|
||||
"[data-hit]:last-child"
|
||||
) as HTMLElement
|
||||
nextItem?.focus()
|
||||
} else {
|
||||
// handle go down for hit items
|
||||
// find if there's a data-hit item after this one
|
||||
const afterItem = findPrevSibling(focusedItem, "[data-hit]")
|
||||
if (afterItem) {
|
||||
// focus the next item
|
||||
afterItem.focus()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Handles starting to type which focuses the input */
|
||||
const handleKeyDown = useCallback(
|
||||
(e: KeyboardEvent) => {
|
||||
if (!chatOpened) {
|
||||
return
|
||||
}
|
||||
// check if shortcut keys were pressed
|
||||
const lowerPressedKey = e.key.toLowerCase()
|
||||
const pressedShortcut = [...shortcutKeys, "Escape"].some(
|
||||
(s) => s.toLowerCase() === lowerPressedKey
|
||||
)
|
||||
if (pressedShortcut) {
|
||||
return
|
||||
}
|
||||
|
||||
const chatElm = getChatWindowElm()
|
||||
if (!chatElm?.contains(document.activeElement)) {
|
||||
return
|
||||
}
|
||||
const focusedItem = chatElm?.querySelector(":focus") as HTMLElement
|
||||
const inputElm = getInputElm()
|
||||
if (inputElm && focusedItem !== inputElm) {
|
||||
focusInput()
|
||||
}
|
||||
},
|
||||
[shortcutKeys, chatOpened, shortcutKeys, getInputElm, focusInput]
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
window.addEventListener("keydown", handleKeyDown)
|
||||
|
||||
return () => {
|
||||
window.removeEventListener("keydown", handleKeyDown)
|
||||
}
|
||||
}, [handleKeyDown])
|
||||
|
||||
useKeyboardShortcut({
|
||||
metakey: false,
|
||||
shortcutKeys: shortcutKeys,
|
||||
checkEditing: false,
|
||||
isLoading: false,
|
||||
action: handleKeyAction,
|
||||
...keyboardProps,
|
||||
})
|
||||
}
|
||||
@@ -379,10 +379,10 @@ export const useChildDocs = ({
|
||||
{!searchResult.length && (
|
||||
<div className="flex flex-col justify-center items-center gap-docs_0.75">
|
||||
<ExclamationCircle className="text-medusa-fg-subtle" />
|
||||
<span className="text-compact-small-plus text-medusa-fg-subtle text-center">
|
||||
<span className="text-compact-small-plus text-medusa-fg-base text-center">
|
||||
No results found matching your query.
|
||||
</span>
|
||||
<span className="text-compact-small text-medusa-fg-muted text-center">
|
||||
<span className="text-compact-small text-medusa-fg-subtle text-center">
|
||||
Try searching with another term or clearing the search.
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -40,7 +40,7 @@ export const useKeyboardShortcut = ({
|
||||
[shortcutKeys]
|
||||
)
|
||||
|
||||
const sidebarShortcut = useCallback(
|
||||
const onKeyDown = useCallback(
|
||||
(e: KeyboardEvent) => {
|
||||
// the event is triggered when an input
|
||||
// autocompletes, and in that case
|
||||
@@ -63,10 +63,10 @@ export const useKeyboardShortcut = ({
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
window.addEventListener("keydown", sidebarShortcut)
|
||||
window.addEventListener("keydown", onKeyDown)
|
||||
|
||||
return () => {
|
||||
window.removeEventListener("keydown", sidebarShortcut)
|
||||
window.removeEventListener("keydown", onKeyDown)
|
||||
}
|
||||
}, [sidebarShortcut])
|
||||
}, [onKeyDown])
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user