Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 77 additions & 5 deletions app/components/assistant-ui/assistant-modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,17 @@

import { BotIcon, ChevronDownIcon } from "lucide-react";

import { type FC, forwardRef } from "react";
import { type FC, forwardRef, useState, useEffect } from "react";
import { AssistantModalPrimitive } from "@assistant-ui/react";
import { usePathname } from "next/navigation";

import { Thread } from "@/app/components/assistant-ui/thread";
import { TooltipIconButton } from "@/app/components/assistant-ui/tooltip-icon-button";
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from "@/app/components/ui/tooltip";

interface AssistantModalProps {
errorMessage?: string;
Expand All @@ -19,11 +25,61 @@ export const AssistantModal: FC<AssistantModalProps> = ({
showSettingsAction = false,
onClearError,
}) => {
const [showBubble, setShowBubble] = useState(false);

useEffect(() => {
// 检查本次访问是否已关闭过气泡
const bubbleClosed = sessionStorage.getItem("ai-bubble-closed");

if (!bubbleClosed) {
// 页面加载后2秒显示气泡提示
const showTimer = setTimeout(() => {
setShowBubble(true);
}, 2000);

// 15秒后自动关闭气泡
const hideTimer = setTimeout(() => {
setShowBubble(false);
sessionStorage.setItem("ai-bubble-closed", "true");
}, 17000); // 2秒显示 + 15秒停留 = 17秒

return () => {
clearTimeout(showTimer);
clearTimeout(hideTimer);
};
}
}, []);

const handleCloseBubble = () => {
setShowBubble(false);
// 记录本次访问已关闭气泡
sessionStorage.setItem("ai-bubble-closed", "true");
};

return (
<AssistantModalPrimitive.Root>
<AssistantModalPrimitive.Anchor className="aui-root aui-modal-anchor fixed right-4 bottom-4 size-14">
{/* 自定义气泡组件 */}
{showBubble && (
<div
className="absolute bottom-17 right-0 z-40 animate-in fade-in-0 slide-in-from-bottom-2 duration-500"
onClick={handleCloseBubble}
>
<div className="relative bg-gray-100/70 backdrop-blur-sm rounded-2xl shadow-lg border-2 border-black px-8 py-5 cursor-pointer hover:shadow-xl transition-all duration-200 hover:scale-105">
<div className="text-base text-gray-800 font-medium whitespace-nowrap">
有问题可以问我哦~
</div>
{/* 气泡尾巴箭头 - 指向下方按钮 */}
<div className="absolute -bottom-2 right-8">
<div className="w-0 h-0 border-l-[8px] border-r-[8px] border-t-[8px] border-l-transparent border-r-transparent border-t-gray-100/70"></div>
<div className="absolute -top-[2px] -left-[2px] w-0 h-0 border-l-[10px] border-r-[10px] border-t-[10px] border-l-transparent border-r-transparent border-t-black"></div>
</div>
</div>
</div>
)}

<AssistantModalPrimitive.Trigger asChild>
<AssistantModalButton />
<AssistantModalButton onCloseBubble={handleCloseBubble} />
</AssistantModalPrimitive.Trigger>
</AssistantModalPrimitive.Anchor>
<AssistantModalPrimitive.Content
Expand All @@ -40,21 +96,37 @@ export const AssistantModal: FC<AssistantModalProps> = ({
);
};

type AssistantModalButtonProps = { "data-state"?: "open" | "closed" };
type AssistantModalButtonProps = {
"data-state"?: "open" | "closed";
onCloseBubble?: () => void;
onClick?: (e: React.MouseEvent) => void;
};

const AssistantModalButton = forwardRef<
HTMLButtonElement,
AssistantModalButtonProps
>(({ "data-state": state, ...rest }, ref) => {
>(({ "data-state": state, onCloseBubble, ...rest }, ref) => {
const tooltip = state === "open" ? "Close Assistant" : "Open Assistant";

const handleClick = (e: React.MouseEvent) => {
// 当点击open按钮时,关闭气泡对话
if (onCloseBubble) {
onCloseBubble();
}
// 继续执行原有的点击事件
if (rest.onClick) {
rest.onClick(e);
}
};

return (
<TooltipIconButton
variant="default"
tooltip={tooltip}
side="left"
{...rest}
className="aui-modal-button size-full rounded-full shadow transition-transform hover:scale-110 active:scale-90"
onClick={handleClick}
className="aui-modal-button size-full rounded-full shadow transition-transform hover:scale-110 active:scale-90 cursor-pointer"
ref={ref}
>
<BotIcon
Expand Down