Files
mnote/wolai-frontend/src/components/breadcrumb.tsx
T

125 lines
5.0 KiB
TypeScript
Raw Normal View History

2025-11-23 10:55:04 +08:00
"use client";
import Link from "next/link";
import { useParams, useSelectedLayoutSegments } from "next/navigation";
2026-02-01 08:47:40 +08:00
import { ChevronRight, MoreHorizontal, Sparkles, Star } from "lucide-react";
2026-01-24 12:32:51 +08:00
import { useConvexAuth, useMutation, useQuery } from "convex/react";
2025-11-23 10:55:04 +08:00
import type { DocumentRecord } from "@/lib/documents";
import { findBreadcrumb } from "@/lib/documents";
2026-02-01 08:47:40 +08:00
import { useBackendHealth } from "@/hooks/use-backend-health";
import { cn } from "@/lib/utils";
2025-11-23 10:55:04 +08:00
import { usePageLayoutStore } from "@/store/page-layout";
2026-02-01 08:47:40 +08:00
import { useAiAgentUiStore } from "@/store/ai-agent-ui";
2026-01-22 18:53:20 +08:00
import { api } from "@/lib/convex/api";
2025-11-23 10:55:04 +08:00
interface BreadcrumbProps {
documents: DocumentRecord[];
}
export function Breadcrumb({ documents }: BreadcrumbProps) {
2026-01-24 12:32:51 +08:00
const { isAuthenticated } = useConvexAuth();
2025-11-23 10:55:04 +08:00
const segments = useSelectedLayoutSegments();
const params = useParams<{ id?: string }>();
const paramId = typeof params?.id === "string" ? params.id : "";
const activeId = paramId || segments?.[1] || "";
const path = findBreadcrumb(documents, activeId);
const showInspector = usePageLayoutStore((state) => state.showInspector);
const toggleInspector = usePageLayoutStore((state) => state.toggleInspector);
2026-02-01 08:47:40 +08:00
const backendStatus = useBackendHealth();
const documentAgentAvailable = useAiAgentUiStore((s) => s.documentAgentAvailable);
const toggleDocumentAgentOpen = useAiAgentUiStore((s) => s.toggleDocumentAgentOpen);
2026-01-24 12:32:51 +08:00
const isStarred = useQuery(
api.documentStars.isStarred,
activeId && isAuthenticated ? { documentId: activeId } : "skip",
);
2026-01-22 18:53:20 +08:00
const toggleStar = useMutation(api.documentStars.toggle);
2025-11-23 10:55:04 +08:00
2026-01-10 23:08:56 +08:00
// 新建页面后,侧边栏数据可能还没同步到 layout(SSR)注入的 documents,导致 findBreadcrumb 暂时为空。
// 这里用 activeId 兜底,避免右上角“收藏/更多”按钮消失。
if (!activeId) {
2025-11-23 10:55:04 +08:00
return <div className="text-sm text-gray-400">请选择一个页面</div>;
}
2026-01-10 23:08:56 +08:00
const displayPath: Array<Pick<DocumentRecord, "id" | "title">> = path.length
? path
: [{ id: activeId, title: "无标题" }];
2025-11-23 10:55:04 +08:00
return (
<div className="flex flex-1 items-center justify-between">
2026-01-18 19:01:31 +08:00
<nav className="flex items-center text-sm text-wolai-text-secondary">
2026-01-10 23:08:56 +08:00
{displayPath.map((node, index) => {
const isLast = index === displayPath.length - 1;
2025-11-23 10:55:04 +08:00
return (
<span key={node.id} className="flex items-center">
{index > 0 && <ChevronRight className="mx-1 h-4 w-4 text-gray-400" />}
{isLast ? (
2026-01-18 19:01:31 +08:00
<span className="font-medium text-wolai-text-primary">{node.title || "无标题"}</span>
2025-11-23 10:55:04 +08:00
) : (
2026-01-18 19:01:31 +08:00
<Link
href={`/documents/${node.id}`}
className="hover:bg-wolai-bg-hover hover:text-wolai-text-primary px-1.5 py-0.5 rounded cursor-pointer transition-colors"
>
2025-11-23 10:55:04 +08:00
{node.title || "无标题"}
</Link>
)}
</span>
);
})}
</nav>
2026-01-18 19:01:31 +08:00
<div className="flex items-center gap-2 text-wolai-text-secondary">
2026-02-01 08:47:40 +08:00
<span
className={cn(
"h-2 w-2 rounded-full",
backendStatus === "ok" ? "bg-green-500" : "bg-red-500",
)}
title={`后端连接:${
backendStatus === "ok"
? "正常"
: backendStatus === "disabled"
? "未配置"
: backendStatus === "error"
? "异常"
: "检测中"
}`}
aria-label="后端连接状态"
/>
2026-04-13 19:21:42 +08:00
<button
type="button"
className={cn(
"rounded-full px-3 py-1 text-sm transition-colors",
documentAgentAvailable
2026-04-13 19:21:42 +08:00
? "bg-[#2563eb] text-white hover:bg-[#1d4ed8]"
: "hover:bg-wolai-bg-hover hover:text-wolai-text-primary",
)}
2026-02-01 08:47:40 +08:00
onClick={() => toggleDocumentAgentOpen()}
disabled={!documentAgentAvailable}
title={documentAgentAvailable ? "打开页面 AI" : "仅在页面编辑区可用"}
>
<Sparkles className="mr-1 inline h-4 w-4" />
页面AI
2026-02-01 08:47:40 +08:00
</button>
2026-01-18 19:01:31 +08:00
<button
type="button"
className="hover:bg-wolai-bg-hover hover:text-wolai-text-primary rounded-full px-3 py-1 text-sm transition-colors"
2026-01-22 18:53:20 +08:00
onClick={() => void toggleStar({ documentId: activeId })}
2026-01-24 12:32:51 +08:00
disabled={!activeId || !isAuthenticated}
2026-01-18 19:01:31 +08:00
>
2026-01-22 18:53:20 +08:00
<Star
className={`mr-1 inline h-4 w-4 ${isStarred ? "text-[#f5a623]" : ""}`}
fill={isStarred ? "currentColor" : "none"}
/>
2025-11-23 10:55:04 +08:00
收藏
</button>
<button
type="button"
2026-01-18 19:01:31 +08:00
className="hover:bg-wolai-bg-hover hover:text-wolai-text-primary rounded-full p-2 transition-colors"
2025-11-23 10:55:04 +08:00
onClick={toggleInspector}
aria-label={showInspector ? "隐藏页面选项" : "显示页面选项"}
title={showInspector ? "隐藏页面选项" : "显示页面选项"}
>
<MoreHorizontal className="h-4 w-4" />
</button>
</div>
</div>
);
}