feat: update knowledge workspace and menu configuration
This commit is contained in:
1 parent
64a5305bbf
commit
8af4fed89d
28 files changed
+1250
-219
No files matched your search
@@ -0,0 +1,71 @@
|
||||
"use client";
|
||||
|
||||
import { markdown, markdownLanguage } from "@codemirror/lang-markdown";
|
||||
import { languages } from "@codemirror/language-data";
|
||||
import { basicLightInit } from "@uiw/codemirror-theme-basic";
|
||||
import { monokaiInit } from "@uiw/codemirror-theme-monokai";
|
||||
import CodeMirror from "@uiw/react-codemirror";
|
||||
import { useTheme } from "next-themes";
|
||||
import { useMemo } from "react";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
import { MarkdownContent } from "@/components/workspace/messages/markdown-content";
|
||||
import { reasoningPlugins } from "@/core/streamdown";
|
||||
|
||||
const lightTheme = basicLightInit({ settings: { background: "transparent", fontSize: "14px" } });
|
||||
const darkTheme = monokaiInit({
|
||||
settings: { background: "transparent", gutterBackground: "transparent", fontSize: "14px" },
|
||||
});
|
||||
|
||||
/** Markdown-aware source editor shared by Wiki create/edit dialogs. */
|
||||
export function MarkdownCodeEditor({
|
||||
value,
|
||||
onChange,
|
||||
className,
|
||||
}: {
|
||||
value: string;
|
||||
onChange: (value: string) => void;
|
||||
className?: string;
|
||||
}) {
|
||||
const { resolvedTheme } = useTheme();
|
||||
const extensions = useMemo(
|
||||
() => [markdown({ base: markdownLanguage, codeLanguages: languages })],
|
||||
[],
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={cn("grid min-h-[46vh] overflow-hidden rounded-lg border bg-background lg:grid-cols-2", className)}>
|
||||
<section className="flex min-h-0 flex-col border-b lg:border-r lg:border-b-0">
|
||||
<div className="flex h-9 shrink-0 items-center border-b bg-muted/40 px-3 text-xs font-medium text-muted-foreground">
|
||||
Markdown 编辑
|
||||
</div>
|
||||
<CodeMirror
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
theme={resolvedTheme === "dark" ? darkTheme : lightTheme}
|
||||
extensions={extensions}
|
||||
basicSetup={{
|
||||
foldGutter: true,
|
||||
highlightActiveLine: true,
|
||||
highlightActiveLineGutter: false,
|
||||
lineNumbers: true,
|
||||
}}
|
||||
className="min-h-[42vh] flex-1 overflow-auto font-mono text-sm [&_.cm-editor]:min-h-[42vh] [&_.cm-focused]:outline-none!"
|
||||
/>
|
||||
</section>
|
||||
<section className="flex min-h-0 flex-col bg-muted/10">
|
||||
<div className="flex h-9 shrink-0 items-center border-b bg-muted/40 px-3 text-xs font-medium text-muted-foreground">
|
||||
实时渲染
|
||||
</div>
|
||||
<div className="min-h-[42vh] flex-1 overflow-auto p-4">
|
||||
<MarkdownContent
|
||||
content={value || "*暂无正文*"}
|
||||
isLoading={false}
|
||||
remarkPlugins={reasoningPlugins.remarkPlugins}
|
||||
rehypePlugins={reasoningPlugins.rehypePlugins}
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -77,7 +77,7 @@ export function WikiHeader({
|
||||
<Menu className="size-5" />
|
||||
</button>
|
||||
<div className="text-base font-semibold tracking-tight text-slate-900 dark:text-slate-100">
|
||||
问答梳理
|
||||
知识库管理
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -204,7 +204,7 @@ export function noteFolderSegments(note: KnowledgeNote): string[] {
|
||||
|
||||
/** Breadcrumb segments for a note (folders only; the title is appended by the caller). */
|
||||
export function noteBreadcrumbSegments(note: KnowledgeNote): string[] {
|
||||
return ["问答梳理", ...noteFolderSegments(note).map(localizeFolderName)];
|
||||
return ["知识库管理", ...noteFolderSegments(note).map(localizeFolderName)];
|
||||
}
|
||||
|
||||
function sortTree(nodes: TreeNode[]): void {
|
||||
|
||||
@@ -117,7 +117,7 @@ export function SpreadsheetPreview({
|
||||
{Array.from({ length: columnCount }, (_, columnIndex) => (
|
||||
<th
|
||||
key={columnIndex}
|
||||
className="border-border bg-muted/60 text-foreground sticky top-0 border px-3 py-1.5 text-left font-medium whitespace-pre-wrap"
|
||||
className="border-border bg-muted/60 text-foreground border px-3 py-1.5 text-left font-semibold whitespace-pre-wrap"
|
||||
>
|
||||
{desensitize(headerRow?.[columnIndex] ?? "")}
|
||||
</th>
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
import Link from "next/link";
|
||||
import { usePathname } from "next/navigation";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { useMemo } from "react";
|
||||
|
||||
import {
|
||||
SidebarGroup,
|
||||
@@ -13,13 +14,18 @@ import {
|
||||
SidebarMenuItem,
|
||||
} from "@/components/ui/sidebar";
|
||||
import { useI18n } from "@/core/i18n/hooks";
|
||||
// import { BotIcon, BrainIcon, ListChecks, MessageSquarePlus, MessagesSquare, SparklesIcon, UsersIcon } from "lucide-react";
|
||||
import { BookOpen, BotIcon, BrainIcon, BriefcaseBusiness, ListChecks, MessageSquarePlus, MessagesSquare, PenLine, Plus, SparklesIcon, Telescope, Users } from "lucide-react";
|
||||
import { BookOpen, BotIcon, BrainIcon, ListChecks, MessageSquarePlus, MessagesSquare, PenLine, Plus, SparklesIcon, Telescope, Users } from "lucide-react";
|
||||
|
||||
import { KnowledgeRagToggle } from "./knowledge-rag-toggle";
|
||||
import { SidebarCollapseButton } from "./workspace-header";
|
||||
import { WorkspaceNavStudio } from "./workspace-nav-studio";
|
||||
import { cn } from "@/lib/utils";
|
||||
import {
|
||||
applyCompactMenuOverrides,
|
||||
type CompactMenuItem,
|
||||
} from "@/core/page-layout/compact-menu-overrides";
|
||||
import { type MenuItem } from "@/core/page-layout/sidebar-menu";
|
||||
import { useCompactMenuOverrides } from "@/strategy-components/api/compact-menu-overrides";
|
||||
|
||||
// 普通导航项:默认中性色,激活态与「新对话」一致——主色浅底 + 主色文字/图标
|
||||
const NAV_ITEM_CLASS =
|
||||
@@ -40,6 +46,99 @@ const NEW_CHAT_COMPACT_CLASS =
|
||||
"hover:bg-[rgb(var(--legacy-primary-rgb)/4%)] hover:text-[var(--primary)] hover:border-[rgb(var(--legacy-primary-rgb)/32%)] " +
|
||||
"data-[active=true]:bg-card data-[active=true]:text-[var(--primary)] data-[active=true]:shadow-none";
|
||||
|
||||
export type WorkspaceCompactNavItem = MenuItem & {
|
||||
match: (pathname: string) => boolean;
|
||||
};
|
||||
|
||||
export const WORKSPACE_COMPACT_NAV_ITEMS: WorkspaceCompactNavItem[] = [
|
||||
{
|
||||
id: "workspace-new-chat",
|
||||
label: "新对话",
|
||||
icon: MessageSquarePlus,
|
||||
path: "/page/workspace/chats/new",
|
||||
match: (pathname) => pathname === "/page/workspace/chats/new",
|
||||
},
|
||||
{
|
||||
id: "workspace-chats",
|
||||
label: "对话",
|
||||
icon: MessagesSquare,
|
||||
path: "/page/workspace/chats",
|
||||
match: (pathname) => pathname === "/page/workspace/chats",
|
||||
},
|
||||
{
|
||||
id: "workspace-agents",
|
||||
label: "智能体",
|
||||
icon: BotIcon,
|
||||
path: "/page/workspace/agents",
|
||||
match: (pathname) => pathname.startsWith("/page/workspace/agents"),
|
||||
},
|
||||
{
|
||||
id: "workspace-skills",
|
||||
label: "技能",
|
||||
icon: SparklesIcon,
|
||||
path: "/page/workspace/skills",
|
||||
match: (pathname) => pathname.startsWith("/page/workspace/skills"),
|
||||
},
|
||||
{
|
||||
id: "workspace-memory",
|
||||
label: "记忆",
|
||||
icon: BrainIcon,
|
||||
path: "/page/workspace/memory",
|
||||
match: (pathname) => pathname.startsWith("/page/workspace/memory"),
|
||||
},
|
||||
{
|
||||
id: "workspace-ai-writing",
|
||||
label: "AI写作",
|
||||
icon: PenLine,
|
||||
path: "/page/workspace/ai-writing",
|
||||
match: (pathname) => pathname.startsWith("/page/workspace/ai-writing"),
|
||||
},
|
||||
{
|
||||
id: "workspace-deep-research",
|
||||
label: "深度研究",
|
||||
icon: Telescope,
|
||||
path: "/page/workspace/deep-research",
|
||||
match: (pathname) => pathname.startsWith("/page/workspace/deep-research"),
|
||||
},
|
||||
{
|
||||
id: "workspace-roundtable-planning",
|
||||
label: "多智能体规划",
|
||||
icon: Users,
|
||||
path: "/page/workspace/roundtable/planning",
|
||||
match: (pathname) => pathname.startsWith("/page/workspace/roundtable/planning"),
|
||||
},
|
||||
{
|
||||
id: "workspace-scheduled-tasks",
|
||||
label: "定时任务",
|
||||
icon: ListChecks,
|
||||
path: "/page/workspace/scheduled/tasks",
|
||||
match: (pathname) => pathname === "/page/workspace/scheduled/tasks",
|
||||
},
|
||||
{
|
||||
id: "workspace-knowledge",
|
||||
label: "知识库管理",
|
||||
icon: BookOpen,
|
||||
path: "/page/workspace/knowledge",
|
||||
match: (pathname) => pathname.startsWith("/page/workspace/knowledge"),
|
||||
},
|
||||
];
|
||||
|
||||
export type EffectiveWorkspaceCompactNavItem = CompactMenuItem & WorkspaceCompactNavItem;
|
||||
|
||||
export function useWorkspaceCompactNavItems(): {
|
||||
sidebarItems: EffectiveWorkspaceCompactNavItem[];
|
||||
settingsItems: EffectiveWorkspaceCompactNavItem[];
|
||||
} {
|
||||
const { data: overrides = [] } = useCompactMenuOverrides();
|
||||
return useMemo(() => {
|
||||
const applied = applyCompactMenuOverrides(WORKSPACE_COMPACT_NAV_ITEMS, overrides);
|
||||
return {
|
||||
sidebarItems: applied.sidebarItems as EffectiveWorkspaceCompactNavItem[],
|
||||
settingsItems: applied.settingsItems as EffectiveWorkspaceCompactNavItem[],
|
||||
};
|
||||
}, [overrides]);
|
||||
}
|
||||
|
||||
export function WorkspaceNavChatList({
|
||||
showCollapseTrigger = false,
|
||||
compact = false,
|
||||
@@ -50,6 +149,8 @@ export function WorkspaceNavChatList({
|
||||
const { t } = useI18n();
|
||||
const pathname = usePathname();
|
||||
const navigate = useNavigate();
|
||||
const { sidebarItems } = useWorkspaceCompactNavItems();
|
||||
const newChatItem = sidebarItems.find((item) => item.id === "workspace-new-chat");
|
||||
// 「新对话」点击时,如果已经在 /chats/new 路径,直接 navigate 会被 react-router
|
||||
// 当成同路径 no-op,不更新 location.key,挂在 location.key 上的下游 hook
|
||||
// (useThreadChat)就感知不到「用户想重开一个对话」,UI 不刷新。
|
||||
@@ -68,171 +169,72 @@ export function WorkspaceNavChatList({
|
||||
<>
|
||||
<SidebarGroup className={cn("px-2", showCollapseTrigger ? "pt-3" : "pt-2")}>
|
||||
<SidebarMenu className="gap-1">
|
||||
<SidebarMenuItem>
|
||||
{compact ? (
|
||||
{(showCollapseTrigger || newChatItem) && (
|
||||
<SidebarMenuItem>
|
||||
{compact ? (
|
||||
// 紧凑模式:新对话占满整行白卡样式 + 右侧收起钮(点击收起整列历史栏)。
|
||||
<div className="flex items-center gap-1">
|
||||
<SidebarMenuButton
|
||||
isActive={pathname === "/page/workspace/chats/new"}
|
||||
asChild
|
||||
className={cn(NEW_CHAT_COMPACT_CLASS, "min-w-0 flex-1")}
|
||||
>
|
||||
<Link href="/page/workspace/chats/new" onClick={handleNewChatClick}>
|
||||
<Plus className="stroke-[2.5] text-[var(--primary)]" />
|
||||
<span>{t.sidebar.newChat}</span>
|
||||
</Link>
|
||||
</SidebarMenuButton>
|
||||
{showCollapseTrigger && (
|
||||
<SidebarCollapseButton className="size-8 shrink-0 rounded-xl" />
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex items-center gap-1">
|
||||
<SidebarMenuButton
|
||||
isActive={pathname === "/page/workspace/chats/new"}
|
||||
asChild
|
||||
className={cn(
|
||||
NEW_CHAT_CLASS,
|
||||
showCollapseTrigger && "min-w-0 flex-1",
|
||||
)}
|
||||
>
|
||||
<Link
|
||||
href="/page/workspace/chats/new"
|
||||
onClick={handleNewChatClick}
|
||||
<div className="flex items-center gap-1">
|
||||
<SidebarMenuButton
|
||||
isActive={pathname === "/page/workspace/chats/new"}
|
||||
asChild
|
||||
className={cn(NEW_CHAT_COMPACT_CLASS, "min-w-0 flex-1")}
|
||||
>
|
||||
<MessageSquarePlus />
|
||||
<span>{t.sidebar.newChat}</span>
|
||||
</Link>
|
||||
</SidebarMenuButton>
|
||||
{showCollapseTrigger && (
|
||||
<SidebarCollapseButton className="size-8 shrink-0 rounded-xl" />
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</SidebarMenuItem>
|
||||
<Link href="/page/workspace/chats/new" onClick={handleNewChatClick}>
|
||||
<Plus className="stroke-[2.5] text-[var(--primary)]" />
|
||||
<span>{newChatItem?.label ?? t.sidebar.newChat}</span>
|
||||
</Link>
|
||||
</SidebarMenuButton>
|
||||
{showCollapseTrigger && (
|
||||
<SidebarCollapseButton className="size-8 shrink-0 rounded-xl" />
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex items-center gap-1">
|
||||
<SidebarMenuButton
|
||||
isActive={pathname === "/page/workspace/chats/new"}
|
||||
asChild
|
||||
className={cn(
|
||||
NEW_CHAT_CLASS,
|
||||
showCollapseTrigger && "min-w-0 flex-1",
|
||||
)}
|
||||
>
|
||||
<Link
|
||||
href="/page/workspace/chats/new"
|
||||
onClick={handleNewChatClick}
|
||||
>
|
||||
<MessageSquarePlus />
|
||||
<span>{newChatItem?.label ?? t.sidebar.newChat}</span>
|
||||
</Link>
|
||||
</SidebarMenuButton>
|
||||
{showCollapseTrigger && (
|
||||
<SidebarCollapseButton className="size-8 shrink-0 rounded-xl" />
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</SidebarMenuItem>
|
||||
)}
|
||||
{!showCollapseTrigger && (
|
||||
<>
|
||||
<SidebarMenuItem>
|
||||
<SidebarMenuButton
|
||||
isActive={pathname === "/page/workspace/chats"}
|
||||
asChild
|
||||
className={NAV_ITEM_CLASS}
|
||||
>
|
||||
<Link href="/page/workspace/chats">
|
||||
<MessagesSquare />
|
||||
<span>{t.sidebar.chats}</span>
|
||||
</Link>
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem>
|
||||
<SidebarMenuItem>
|
||||
<SidebarMenuButton
|
||||
isActive={pathname.startsWith("/page/workspace/agents")}
|
||||
asChild
|
||||
className={NAV_ITEM_CLASS}
|
||||
>
|
||||
<Link href="/page/workspace/agents">
|
||||
<BotIcon />
|
||||
<span>{t.sidebar.agents}</span>
|
||||
</Link>
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem>
|
||||
<SidebarMenuItem>
|
||||
<SidebarMenuButton
|
||||
isActive={pathname.startsWith("/page/workspace/skills")}
|
||||
asChild
|
||||
className={NAV_ITEM_CLASS}
|
||||
>
|
||||
<Link href="/page/workspace/skills">
|
||||
<SparklesIcon />
|
||||
<span>技能</span>
|
||||
</Link>
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem>
|
||||
<SidebarMenuItem>
|
||||
<SidebarMenuButton
|
||||
isActive={pathname.startsWith("/page/workspace/memory")}
|
||||
asChild
|
||||
className={NAV_ITEM_CLASS}
|
||||
>
|
||||
<Link href="/page/workspace/memory">
|
||||
<BrainIcon />
|
||||
<span>记忆</span>
|
||||
</Link>
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem>
|
||||
<SidebarMenuItem>
|
||||
<SidebarMenuButton
|
||||
isActive={pathname.startsWith("/page/workspace/ai-writing")}
|
||||
asChild
|
||||
className={NAV_ITEM_CLASS}
|
||||
>
|
||||
<Link href="/page/workspace/ai-writing">
|
||||
<PenLine />
|
||||
<span>AI写作</span>
|
||||
</Link>
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem>
|
||||
<SidebarMenuItem>
|
||||
<SidebarMenuButton
|
||||
isActive={pathname.startsWith("/page/workspace/deep-research")}
|
||||
asChild
|
||||
className={NAV_ITEM_CLASS}
|
||||
>
|
||||
<Link href="/page/workspace/deep-research">
|
||||
<Telescope />
|
||||
<span>深度研究</span>
|
||||
</Link>
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem>
|
||||
<SidebarMenuItem>
|
||||
<SidebarMenuButton
|
||||
isActive={pathname.startsWith("/page/workspace/roundtable/planning")}
|
||||
asChild
|
||||
className={NAV_ITEM_CLASS}
|
||||
>
|
||||
<Link href="/page/workspace/roundtable/planning">
|
||||
<Users />
|
||||
<span>多智能体规划</span>
|
||||
</Link>
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem>
|
||||
{/* <SidebarMenuItem>
|
||||
<SidebarMenuButton
|
||||
isActive={pathname.startsWith("/page/workspace/roundtable/position")}
|
||||
asChild
|
||||
className={NAV_ITEM_CLASS}
|
||||
>
|
||||
<Link href="/page/workspace/roundtable/position">
|
||||
<BriefcaseBusiness />
|
||||
<span>岗位会商</span>
|
||||
</Link>
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem> */}
|
||||
<SidebarMenuItem>
|
||||
<SidebarMenuButton
|
||||
isActive={pathname === "/page/workspace/scheduled/tasks"}
|
||||
asChild
|
||||
className={NAV_ITEM_CLASS}
|
||||
>
|
||||
<Link href="/page/workspace/scheduled/tasks">
|
||||
<ListChecks />
|
||||
<span>定时任务</span>
|
||||
</Link>
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem>
|
||||
{sidebarItems
|
||||
.filter((item) => item.id !== "workspace-new-chat")
|
||||
.map((item) => {
|
||||
const Icon = item.icon;
|
||||
return (
|
||||
<SidebarMenuItem key={item.id}>
|
||||
<SidebarMenuButton
|
||||
isActive={item.match(pathname)}
|
||||
asChild
|
||||
className={NAV_ITEM_CLASS}
|
||||
>
|
||||
<Link href={item.path ?? "/page/workspace/chats/new"}>
|
||||
<Icon />
|
||||
<span>{item.label}</span>
|
||||
</Link>
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem>
|
||||
);
|
||||
})}
|
||||
<KnowledgeRagToggle className={NAV_ITEM_CLASS} />
|
||||
<SidebarMenuItem>
|
||||
<SidebarMenuButton
|
||||
isActive={pathname.startsWith("/page/workspace/knowledge")}
|
||||
asChild
|
||||
className={NAV_ITEM_CLASS}
|
||||
>
|
||||
<Link href="/page/workspace/knowledge">
|
||||
<BookOpen />
|
||||
<span>问答梳理</span>
|
||||
</Link>
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem>
|
||||
</>
|
||||
)}
|
||||
</SidebarMenu>
|
||||
|
||||
@@ -46,6 +46,7 @@ import { NotificationDialog } from "./notification-dialog";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
|
||||
import { SettingsDialog } from "./settings";
|
||||
import { useWorkspaceCompactNavItems } from "./workspace-nav-chat-list";
|
||||
|
||||
function NavMenuButtonContent({
|
||||
isSidebarOpen,
|
||||
@@ -80,6 +81,7 @@ export function WorkspaceNavMenu() {
|
||||
const { open: isSidebarOpen } = useSidebar();
|
||||
const { t } = useI18n();
|
||||
const { data: unreadCount = 0 } = useUnreadCount();
|
||||
const { settingsItems } = useWorkspaceCompactNavItems();
|
||||
|
||||
useEffect(() => {
|
||||
setMounted(true);
|
||||
@@ -129,6 +131,18 @@ export function WorkspaceNavMenu() {
|
||||
sideOffset={4}
|
||||
>
|
||||
<DropdownMenuGroup>
|
||||
{settingsItems.map((item) => {
|
||||
const Icon = item.icon;
|
||||
return (
|
||||
<DropdownMenuItem
|
||||
key={item.id}
|
||||
onClick={() => item.path && navigate(item.path)}
|
||||
>
|
||||
<Icon />
|
||||
{item.label}
|
||||
</DropdownMenuItem>
|
||||
);
|
||||
})}
|
||||
<DropdownMenuItem onClick={() => setNotificationOpen(true)}>
|
||||
<BellIcon />
|
||||
{t.notifications.title}
|
||||
|
||||
Reference in new issue
Block a user