docs: improve ai chat inbox ui (#5244)

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
Bereket Engida
2025-10-11 20:33:23 -07:00
committed by GitHub
parent d3a5e360db
commit 91c68c0af4

View File

@@ -11,7 +11,7 @@ import {
useRef, useRef,
useState, useState,
} from "react"; } from "react";
import { Loader2, SearchIcon, Send, X } from "lucide-react"; import { Loader2, SearchIcon, Send, Trash2, X } from "lucide-react";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
import { buttonVariants } from "fumadocs-ui/components/ui/button"; import { buttonVariants } from "fumadocs-ui/components/ui/button";
import Link from "fumadocs-core/link"; import Link from "fumadocs-core/link";
@@ -33,30 +33,30 @@ function useChatContext() {
return use(Context)!.chat; return use(Context)!.chat;
} }
function SearchAIActions() { // function SearchAIActions() {
const { messages, status, setMessages, stop } = useChatContext(); // const { messages, status, setMessages, stop } = useChatContext();
const isGenerating = status === "streaming" || status === "submitted"; // const isGenerating = status === "streaming" || status === "submitted";
if (messages.length === 0) return null; // if (messages.length === 0) return null;
return ( // return (
<> // <>
<button // <button
type="button" // type="button"
className={cn( // className={cn(
buttonVariants({ // buttonVariants({
color: "secondary", // color: "secondary",
size: "sm", // size: "sm",
className: "rounded-none", // className: "rounded-none",
}), // }),
)} // )}
onClick={isGenerating ? stop : () => setMessages([])} // onClick={isGenerating ? stop : () => setMessages([])}
> // >
{isGenerating ? "Cancel" : "Clear Chat"} // {isGenerating ? "Cancel" : "Clear Chat"}
</button> // </button>
</> // </>
); // );
} // }
const suggestions = [ const suggestions = [
"How to configure Sqlite database?", "How to configure Sqlite database?",
@@ -66,7 +66,7 @@ const suggestions = [
]; ];
function SearchAIInput(props: ComponentProps<"form">) { function SearchAIInput(props: ComponentProps<"form">) {
const { status, sendMessage, stop, messages } = useChatContext(); const { status, sendMessage, stop, messages, setMessages } = useChatContext();
const [input, setInput] = useState(""); const [input, setInput] = useState("");
const isLoading = status === "streaming" || status === "submitted"; const isLoading = status === "streaming" || status === "submitted";
const showSuggestions = messages.length === 0 && !isLoading; const showSuggestions = messages.length === 0 && !isLoading;
@@ -81,12 +81,22 @@ function SearchAIInput(props: ComponentProps<"form">) {
void sendMessage({ text: suggestion }); void sendMessage({ text: suggestion });
}; };
const handleClear = () => {
setMessages([]);
setInput("");
};
useEffect(() => { useEffect(() => {
if (isLoading) document.getElementById("nd-ai-input")?.focus(); if (isLoading) document.getElementById("nd-ai-input")?.focus();
}, [isLoading]); }, [isLoading]);
return ( return (
<div className={cn("flex flex-col", isLoading ? "opacity-50" : "")}> <div
className={cn(
"flex flex-col relative bg-fd-background m-[1px] h-full border border-fd-border rounded-lg shadow-2xl shadow-fd-background",
isLoading ? "opacity-50" : "",
)}
>
<form <form
{...props} {...props}
className={cn("flex items-start pe-2", props.className)} className={cn("flex items-start pe-2", props.className)}
@@ -94,9 +104,9 @@ function SearchAIInput(props: ComponentProps<"form">) {
> >
<Input <Input
value={input} value={input}
placeholder={isLoading ? "answering..." : "Ask BA bot"} placeholder={isLoading ? "answering..." : "Ask BA Bot"}
autoFocus autoFocus
className="p-4" className="p-4 text-sm"
disabled={status === "streaming" || status === "submitted"} disabled={status === "streaming" || status === "submitted"}
onChange={(e) => { onChange={(e) => {
setInput(e.target.value); setInput(e.target.value);
@@ -156,6 +166,38 @@ function SearchAIInput(props: ComponentProps<"form">) {
</div> </div>
</div> </div>
)} )}
{showSuggestions && (
<div className="border-t px-4 text-xs text-fd-muted-foreground bg-fd-accent/40 h-full flex items-center gap-1 mt-2 py-1">
Powered by{" "}
<Link
href="https://inkeep.com"
target="_blank"
rel="noopener noreferrer"
className="text-fd-primary hover:text-fd-primary/80 hover:underline"
>
Inkeep.
</Link>
AI can be inaccurate, please verify the information.
</div>
)}
{!showSuggestions && (
<div className="border-t px-4 text-xs text-fd-muted-foreground cursor-pointer bg-fd-accent/40 h-full flex items-center gap-1 mt-2 py-1">
<div
className="flex items-center gap-1 empty:hidden hover:text-fd-foreground transition-all duration-200 aria-disabled:opacity-50 aria-disabled:cursor-not-allowed"
role="button"
aria-disabled={isLoading}
tabIndex={0}
onClick={() => {
if (!isLoading) {
handleClear();
}
}}
>
<Trash2 className="size-3" />
<p>Clear</p>
</div>
</div>
)}
</div> </div>
); );
} }
@@ -463,28 +505,8 @@ export function AISearchTrigger() {
} }
}} }}
> >
<p className="text-xs absolute -top-3 lg:top-auto lg:left-auto left-4 lg:-bottom-3 lg:right-4 flex items-center gap-1 flex-1 text-fd-muted-foreground">
Powered by{" "}
<Link
href="https://inkeep.com"
target="_blank"
aria-label="Inkeep"
>
<InKeepLogo className="w-16 h-16" />
</Link>
</p>
<div className="sticky top-0 flex gap-2 items-center py-2 w-[min(800px,90vw)]"> <div className="sticky top-0 flex gap-2 items-center py-2 w-[min(800px,90vw)]">
<div className="flex justify-between w-full items-center"> <div className="flex justify-end w-full items-center">
<p className="text-xs flex items-center gap-1 flex-1 text-fd-muted-foreground">
Powered by{" "}
<Link
href="https://inkeep.com"
target="_blank"
aria-label="Inkeep"
>
<InKeepLogo className="w-16 h-16" />
</Link>
</p>
<button <button
aria-label="Close" aria-label="Close"
tabIndex={-1} tabIndex={-1}
@@ -540,9 +562,9 @@ export function AISearchTrigger() {
</Presence> </Presence>
<div <div
className={cn( className={cn(
"fixed bottom-2 transition-[width,height] duration-300 ease-[cubic-bezier(0.34,1.56,0.64,1)] -translate-x-1/2 rounded-sm border shadow-xl overflow-hidden z-30", "fixed bottom-4 transition-[width,height] duration-300 ease-[cubic-bezier(0.34,1.56,0.64,1)] -translate-x-1/2 rounded-sm border shadow-xl overflow-hidden z-30",
open open
? `w-[min(800px,90vw)] bg-fd-popover ${showSuggestions ? "h-48" : "h-32"}` ? `w-[min(800px,90vw)] bg-fd-accent/30 ${showSuggestions ? "h-1/4" : "h-32"}`
: "w-40 h-10 bg-fd-secondary text-fd-secondary-foreground shadow-fd-background rounded-2xl", : "w-40 h-10 bg-fd-secondary text-fd-secondary-foreground shadow-fd-background rounded-2xl",
)} )}
style={{ style={{
@@ -571,9 +593,6 @@ export function AISearchTrigger() {
)} )}
> >
<SearchAIInput className="flex-1" /> <SearchAIInput className="flex-1" />
<div className="flex items-center gap-1.5 p-2 empty:hidden">
<SearchAIActions />
</div>
</div> </div>
</Presence> </Presence>
</div> </div>