mirror of
https://github.com/LukeHagar/better-auth.git
synced 2025-12-06 04:19:20 +00:00
docs: improve ai chat inbox ui (#5244)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user