export function ToolGuide({ onTabChange }: { onTabChange: (tab: string) => void }) {
const driverRef = useRef<ReturnType | null>(null);
const isGuidingRef = useRef(false);
const { needShowProjectGuide, setNeedShowProjectGuide } = useGuideStore();
const initDriver = () => {
try {
const checkElementsExist = () => {
const step1 = document.querySelector("#step-summarization");
const step2 = document.querySelector("#step-mindmap");
const step3 = document.querySelector("#step-flownote");
const step4 = document.querySelector("#step-quizzes");
const step5 = document.querySelector("#step-flashcards");
return step1 && step2 && step3 && step4 && step5;
};
if (!checkElementsExist()) {
console.warn("Guide elements not found, skipping guide");
return;
}
const driverObj = driver({
allowClose: false,
overlayOpacity: 0,
stagePadding: 0,
stageRadius: 8,
allowKeyboardControl: false,
disableActiveInteraction: false,
showButtons: [],
showProgress: false,
steps: [
{
element: "#step-summarization",
popover: {
title: "Summary",
side: "top",
align: "start",
},
},
{
element: "#step-mindmap",
popover: {
description: "AI Mindmap for Visualizing Complex Topics CuFlow converts your study materials into clean, layered mindmaps, making relationships intuitive and boosting memory retention.",
side: "right",
align: "start",
},
},
{
element: "#step-flownote",
popover: {
description: "FlowNote for Automatic, Intelligent Note-Taking FlowNote captures your learning process and turns it into clean, structured notes—continuously and effortlessly.",
side: "bottom",
align: "end",
},
},
{
element: "#step-quizzes",
popover: {
description: "Test understanding with AI-predicted quizzes CuFlow creates grounded practice questions with explanations and difficulty ratings, helping you quickly assess what you know and what needs work.",
side: "bottom",
align: "end",
},
},
{
element: "#step-flashcards",
popover: {
description: "",
side: "bottom",
align: "end",
},
}
],
onDestroyStarted: () => {
isGuidingRef.current = false;
},
onPopoverRender: (popover, opts) => {
console.log('popover', popover);
console.log('opts', opts);
// 隐藏原本的 title/description
if (popover.title) popover.title.style.display = "none";
if (popover.description) popover.description.style.display = "none";
if(popover.footerButtons) popover.footerButtons.style.display = "none";
// 清理已有自定义内容
const existingCustom = popover.wrapper.querySelector(".driver-custom-content");
const footer = popover.wrapper.querySelector(".driver-custom-footer");
if (existingCustom) {
existingCustom.remove();
}
if (footer) {
footer.remove();
}
popover.title.remove();
popover.description.remove();
popover.footerButtons.remove();
popover.previousButton.remove();
popover.nextButton.remove();
popover.closeButton.remove();
popover.footer.remove();
popover.progress.remove();
// 使用 React 组件渲染自定义内容
const mountPoint = document.createElement("div");
mountPoint.className = "driver-custom-content";
popover.wrapper.appendChild(mountPoint);
const root = createRoot(mountPoint);
// 先让 wrapper 隐藏,避免在重新定位前闪烁
popover.wrapper.style.opacity = '0';
root.render(
<div className="w-full h-full bg-white flex flex-col items-center justify-center relative gap-2">
<span
className="mt-[40px] w-full justify-start text-slate-900 text-sm font-normal font-Gloria-Hallelujah leading-7"
style={{ fontFamily: '"Gloria Hallelujah","Helvetica","sans-serif"' }}
>
{popover.description.textContent}
</span>
<img src="/img/guide/guide.svg" className="w-[127px] h-auto absolute top-[-65px] right-0" alt="" />
<div className="w-full h-full flex items-center justify-end cursor-pointer">
<button className="bg-black text-white px-4 py-2 rounded-md"
onClick={() => {
const activeElement = driverRef.current?.getActiveElement();
// 根据当前步骤更新 URL
if (activeElement?.id === "step-summarization") {
onTabChange('mindmap');
driverObj.moveNext();
// 不调用 moveTo,让 driver 自动继续到下一步
} else if (activeElement?.id === "step-mindmap") {
onTabChange('flownote');
driverObj.moveNext();
// 不调用 moveTo,让 driver 自动继续到下一步
} else if (activeElement?.id === "step-flownote") {
onTabChange('quizzes');
driverObj.moveNext();
// 不调用 moveTo,让 driver 自动继续到下一步
} else if (activeElement?.id === "step-quizzes") {
onTabChange('flashcards');
driverObj.moveNext();
// 不调用 moveTo,让 driver 自动继续到下一步
} else if (activeElement?.id === "step-flashcards") {
setNeedShowProjectGuide(true);
driverObj.destroy();
}
}}>
Next
</button>
</div>
</div>
);
// 等待 React 渲染完成后,重新计算位置并显示
setTimeout(() => {
// driverRef.current?.refresh();
popover.wrapper.style.opacity = '1';
}, 20);
},
});
driverRef.current = driverObj;
isGuidingRef.current = true; // 标记开始引导
// 延迟执行,确保 DOM 已渲染
const timer = setTimeout(() => {
if (driverRef.current) {
driverRef.current.drive();
}
}, 100);
return () => {
clearTimeout(timer);
};
} catch (error) {
console.error("Failed to initialize driver.js:", error);
}
};
const cleanup = initDriver();
// 清理函数
return () => {
if (cleanup) cleanup();
// 注意:不要在这里销毁 driver,因为 URL 变化时会触发清理
// 只有在组件真正卸载时才销毁
};
"use client";
import { useEffect, useRef } from "react";
import { driver } from "driver.js";
import "driver.js/dist/driver.css";
import { useRouter, useSearchParams, usePathname } from "next/navigation";
import { createRoot } from "react-dom/client";
import { useGuideStore } from "@/common/store/guideStore";
export function ToolGuide({ onTabChange }: { onTabChange: (tab: string) => void }) {
const driverRef = useRef<ReturnType | null>(null);
const isGuidingRef = useRef(false);
const { needShowProjectGuide, setNeedShowProjectGuide } = useGuideStore();
useEffect(() => {
const shown = localStorage.getItem("intro-shown");
if (shown) return;
if (isGuidingRef.current && driverRef.current) {
return;
}
}, []); // 移除 searchParams 和 pathname 依赖,只在组件挂载时初始化一次
// 组件卸载时清理
useEffect(() => {
return () => {
if (driverRef.current) {
driverRef.current.destroy();
driverRef.current = null;
isGuidingRef.current = false;
}
};
}, []);
return null;
}