Merge branch 'dhk-1' of http://192.168.0.200/knowledge-base-bj/deerflow-server into dhk-1
This commit is contained in:
commit
e1da39253a
10 files changed
+161
-101
No files matched your search
@@ -54,7 +54,7 @@ export function PageSidebarDark({ collapsed, onToggleCollapsed }: PageSidebarDar
|
|||||||
};
|
};
|
||||||
|
|
||||||
const hasActiveChild = (item: MenuItem): boolean => {
|
const hasActiveChild = (item: MenuItem): boolean => {
|
||||||
if (item.path && pathname === item.path) {
|
if (item.path && (pathname === item.path || pathname.startsWith(item.path + "/"))) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -153,7 +153,7 @@ export function PageSidebarDark({ collapsed, onToggleCollapsed }: PageSidebarDar
|
|||||||
inWujieEnv
|
inWujieEnv
|
||||||
? item.id === activeTopId
|
? item.id === activeTopId
|
||||||
: item.path
|
: item.path
|
||||||
? pathname === item.path
|
? pathname === item.path || pathname.startsWith(item.path + "/")
|
||||||
: hasActiveChild(item);
|
: hasActiveChild(item);
|
||||||
const handleSidebarItemClick = (item: MenuItem) => {
|
const handleSidebarItemClick = (item: MenuItem) => {
|
||||||
if (inWujieEnv) {
|
if (inWujieEnv) {
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ type PageSidebarProps = {
|
|||||||
type SettingsSection = "account" | "appearance" | "memory" | "tools" | "about";
|
type SettingsSection = "account" | "appearance" | "memory" | "tools" | "about";
|
||||||
|
|
||||||
const hasActiveChild = (item: MenuItem, pathname: string): boolean => {
|
const hasActiveChild = (item: MenuItem, pathname: string): boolean => {
|
||||||
if (item.path && pathname === item.path) {
|
if (item.path && (pathname === item.path || pathname.startsWith(item.path + "/"))) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return item.children?.some((child) => hasActiveChild(child, pathname)) ?? false;
|
return item.children?.some((child) => hasActiveChild(child, pathname)) ?? false;
|
||||||
@@ -116,7 +116,9 @@ function SecondaryNavSidebar() {
|
|||||||
const secondaryItems = activeTopItem?.children ?? [];
|
const secondaryItems = activeTopItem?.children ?? [];
|
||||||
|
|
||||||
const isItemActive = (item: MenuItem) =>
|
const isItemActive = (item: MenuItem) =>
|
||||||
item.path ? pathname === item.path : hasActiveChild(item, pathname);
|
item.path
|
||||||
|
? pathname === item.path || pathname.startsWith(item.path + "/")
|
||||||
|
: hasActiveChild(item, pathname);
|
||||||
|
|
||||||
const handleItemClick = (item: MenuItem) => {
|
const handleItemClick = (item: MenuItem) => {
|
||||||
const path = item.path ?? findFirstRoutePath(item.children);
|
const path = item.path ?? findFirstRoutePath(item.children);
|
||||||
@@ -210,7 +212,7 @@ function SecondaryNavSidebar() {
|
|||||||
{thirdItems.map((child) => {
|
{thirdItems.map((child) => {
|
||||||
const ChildIcon = child.icon;
|
const ChildIcon = child.icon;
|
||||||
const childActive = child.path
|
const childActive = child.path
|
||||||
? pathname === child.path
|
? pathname === child.path || pathname.startsWith(child.path + "/")
|
||||||
: hasActiveChild(child, pathname);
|
: hasActiveChild(child, pathname);
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import type { Message } from "@langchain/langgraph-sdk";
|
import type { Message } from "@langchain/langgraph-sdk";
|
||||||
import type { BaseStream } from "@langchain/langgraph-sdk/react";
|
import type { BaseStream } from "@langchain/langgraph-sdk/react";
|
||||||
import { CheckIcon, SendIcon, XIcon } from "lucide-react";
|
import { CheckIcon, SendIcon, XIcon } from "lucide-react";
|
||||||
import { useCallback, useContext, useEffect, useMemo, useState } from "react";
|
import { useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
|
||||||
|
import { useStickToBottomContext } from "use-stick-to-bottom";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Conversation,
|
Conversation,
|
||||||
@@ -52,6 +53,38 @@ import { SubtaskCard } from "./subtask-card";
|
|||||||
export const MESSAGE_LIST_DEFAULT_PADDING_BOTTOM = 160;
|
export const MESSAGE_LIST_DEFAULT_PADDING_BOTTOM = 160;
|
||||||
export const MESSAGE_LIST_FOLLOWUPS_EXTRA_PADDING_BOTTOM = 80;
|
export const MESSAGE_LIST_FOLLOWUPS_EXTRA_PADDING_BOTTOM = 80;
|
||||||
|
|
||||||
|
/** 用户发送新问题时强制滚到底部(即使用户此前上滑看过历史)。 */
|
||||||
|
function ScrollToBottomOnUserMessage({
|
||||||
|
threadId,
|
||||||
|
messages,
|
||||||
|
}: {
|
||||||
|
threadId: string;
|
||||||
|
messages: Message[];
|
||||||
|
}) {
|
||||||
|
const { scrollToBottom } = useStickToBottomContext();
|
||||||
|
const prevHumanCountRef = useRef<number | null>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
prevHumanCountRef.current = null;
|
||||||
|
}, [threadId]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const humanCount = messages.reduce(
|
||||||
|
(count, message) => count + (message.type === "human" ? 1 : 0),
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
const prev = prevHumanCountRef.current;
|
||||||
|
prevHumanCountRef.current = humanCount;
|
||||||
|
if (prev !== null && humanCount > prev) {
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
requestAnimationFrame(() => scrollToBottom());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [messages, scrollToBottom]);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
export function MessageList({
|
export function MessageList({
|
||||||
className,
|
className,
|
||||||
contentClassName,
|
contentClassName,
|
||||||
@@ -266,6 +299,7 @@ export function MessageList({
|
|||||||
<Conversation
|
<Conversation
|
||||||
className={cn("flex size-full flex-col justify-center", className)}
|
className={cn("flex size-full flex-col justify-center", className)}
|
||||||
>
|
>
|
||||||
|
<ScrollToBottomOnUserMessage threadId={threadId} messages={messages} />
|
||||||
<ConversationContent
|
<ConversationContent
|
||||||
className={cn(
|
className={cn(
|
||||||
"mx-auto w-full max-w-(--container-width-lg) gap-8 pt-12",
|
"mx-auto w-full max-w-(--container-width-lg) gap-8 pt-12",
|
||||||
|
|||||||
@@ -165,7 +165,7 @@ export function WorkspaceNavMenu() {
|
|||||||
Checkpoint 迁移
|
Checkpoint 迁移
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
)}
|
)}
|
||||||
{isAdmin && (
|
{isAdmin ? (
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
navigate("/page/strategy/admin/user-leaderboard")
|
navigate("/page/strategy/admin/user-leaderboard")
|
||||||
@@ -174,6 +174,15 @@ export function WorkspaceNavMenu() {
|
|||||||
<TrophyIcon />
|
<TrophyIcon />
|
||||||
用户排行榜
|
用户排行榜
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
|
) : (
|
||||||
|
<DropdownMenuItem
|
||||||
|
onClick={() =>
|
||||||
|
navigate("/page/strategy/user-leaderboard")
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<TrophyIcon />
|
||||||
|
用户排行榜
|
||||||
|
</DropdownMenuItem>
|
||||||
)}
|
)}
|
||||||
{isAdmin && (
|
{isAdmin && (
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ export function WorkspaceSidebar({
|
|||||||
variant="sidebar"
|
variant="sidebar"
|
||||||
collapsible="none"
|
collapsible="none"
|
||||||
className={cn(
|
className={cn(
|
||||||
"h-full w-(--sidebar-width)",
|
"h-full w-(--sidebar-width) border-r border-border",
|
||||||
useWhiteSidebar &&
|
useWhiteSidebar &&
|
||||||
"[&_[data-slot=sidebar-inner]]:bg-white [&_[data-slot=sidebar-container]]:bg-white",
|
"[&_[data-slot=sidebar-inner]]:bg-white [&_[data-slot=sidebar-container]]:bg-white",
|
||||||
className,
|
className,
|
||||||
|
|||||||
@@ -43,6 +43,8 @@ export type MenuItem = {
|
|||||||
label: string;
|
label: string;
|
||||||
icon: ElementType;
|
icon: ElementType;
|
||||||
path?: string;
|
path?: string;
|
||||||
|
/** 非 admin 用户使用的路由(菜单对普通用户可见时生效,与简洁模式公开排行榜一致) */
|
||||||
|
userPath?: string;
|
||||||
adminOnly?: boolean;
|
adminOnly?: boolean;
|
||||||
children?: MenuItem[];
|
children?: MenuItem[];
|
||||||
};
|
};
|
||||||
@@ -81,18 +83,18 @@ export const sidebarMenuItems: MenuItem[] = [
|
|||||||
label: "XK中任务",
|
label: "XK中任务",
|
||||||
icon: ClipboardList,
|
icon: ClipboardList,
|
||||||
children: [
|
children: [
|
||||||
{
|
// {
|
||||||
id: "xk-task-jc",
|
// id: "xk-task-jc",
|
||||||
label: "任务JC",
|
// label: "任务JC",
|
||||||
icon: ListTodo,
|
// icon: ListTodo,
|
||||||
path: "/page/strategy/task-management/xk-task-jc",
|
// path: "/page/strategy/task-management/xk-task-jc",
|
||||||
},
|
// },
|
||||||
{
|
// {
|
||||||
id: "xk-task-dispatch",
|
// id: "xk-task-dispatch",
|
||||||
label: "任务调度",
|
// label: "任务调度",
|
||||||
icon: ListTodo,
|
// icon: ListTodo,
|
||||||
path: "/page/strategy/task-management/xk-task-dispatch",
|
// path: "/page/strategy/task-management/xk-task-dispatch",
|
||||||
},
|
// },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -125,24 +127,24 @@ export const sidebarMenuItems: MenuItem[] = [
|
|||||||
label: "PG中任务",
|
label: "PG中任务",
|
||||||
icon: Gauge,
|
icon: Gauge,
|
||||||
children: [
|
children: [
|
||||||
{
|
// {
|
||||||
id: "pg-task-pg",
|
// id: "pg-task-pg",
|
||||||
label: "任务PG",
|
// label: "任务PG",
|
||||||
icon: ListTodo,
|
// icon: ListTodo,
|
||||||
path: "/page/strategy/task-management/effect-task-pg",
|
// path: "/page/strategy/task-management/effect-task-pg",
|
||||||
},
|
// },
|
||||||
{
|
// {
|
||||||
id: "pg-task-xdpg",
|
// id: "pg-task-xdpg",
|
||||||
label: "XDPG",
|
// label: "XDPG",
|
||||||
icon: ListTodo,
|
// icon: ListTodo,
|
||||||
path: "/page/strategy/task-management/effect-action-pg",
|
// path: "/page/strategy/task-management/effect-action-pg",
|
||||||
},
|
// },
|
||||||
{
|
// {
|
||||||
id: "pg-task-info-pg",
|
// id: "pg-task-info-pg",
|
||||||
label: "信息PG",
|
// label: "信息PG",
|
||||||
icon: ListTodo,
|
// icon: ListTodo,
|
||||||
path: "/page/strategy/task-management/effect-info-pg",
|
// path: "/page/strategy/task-management/effect-info-pg",
|
||||||
},
|
// },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -150,24 +152,24 @@ export const sidebarMenuItems: MenuItem[] = [
|
|||||||
label: "TY中任务",
|
label: "TY中任务",
|
||||||
icon: Radar,
|
icon: Radar,
|
||||||
children: [
|
children: [
|
||||||
{
|
// {
|
||||||
id: "ty-task-xdty",
|
// id: "ty-task-xdty",
|
||||||
label: "XDTY任务",
|
// label: "XDTY任务",
|
||||||
icon: ListTodo,
|
// icon: ListTodo,
|
||||||
path: "/page/strategy/task-management/mnty-task-mechanism",
|
// path: "/page/strategy/task-management/mnty-task-mechanism",
|
||||||
},
|
// },
|
||||||
{
|
// {
|
||||||
id: "ty-task-plan",
|
// id: "ty-task-plan",
|
||||||
label: "方案TY任务",
|
// label: "方案TY任务",
|
||||||
icon: ListTodo,
|
// icon: ListTodo,
|
||||||
path: "/page/strategy/task-management/mnty-task-plan",
|
// path: "/page/strategy/task-management/mnty-task-plan",
|
||||||
},
|
// },
|
||||||
{
|
// {
|
||||||
id: "ty-task-xxty",
|
// id: "ty-task-xxty",
|
||||||
label: "XXTY任务",
|
// label: "XXTY任务",
|
||||||
icon: ListTodo,
|
// icon: ListTodo,
|
||||||
path: "/page/strategy/task-management/mnty-task-zsty",
|
// path: "/page/strategy/task-management/mnty-task-zsty",
|
||||||
},
|
// },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -197,6 +199,13 @@ export const sidebarMenuItems: MenuItem[] = [
|
|||||||
label: "综合管理",
|
label: "综合管理",
|
||||||
icon: Settings,
|
icon: Settings,
|
||||||
children: [
|
children: [
|
||||||
|
{
|
||||||
|
id: "mgmt-user-leaderboard",
|
||||||
|
label: "用户排行榜",
|
||||||
|
icon: Trophy,
|
||||||
|
path: "/page/strategy/admin/user-leaderboard",
|
||||||
|
userPath: "/page/strategy/user-leaderboard",
|
||||||
|
},
|
||||||
{ id: "mgmt-agents", label: "智能体管理", icon: Bot, path: "/page/strategy/agents" },
|
{ id: "mgmt-agents", label: "智能体管理", icon: Bot, path: "/page/strategy/agents" },
|
||||||
{
|
{
|
||||||
id: "mgmt-business-chain-mapping",
|
id: "mgmt-business-chain-mapping",
|
||||||
@@ -232,13 +241,6 @@ export const sidebarMenuItems: MenuItem[] = [
|
|||||||
path: "/page/strategy/admin/users",
|
path: "/page/strategy/admin/users",
|
||||||
adminOnly: true,
|
adminOnly: true,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
id: "mgmt-user-leaderboard",
|
|
||||||
label: "用户排行榜",
|
|
||||||
icon: Trophy,
|
|
||||||
path: "/page/strategy/admin/user-leaderboard",
|
|
||||||
adminOnly: true,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
id: "mgmt-admin-scheduled-tasks",
|
id: "mgmt-admin-scheduled-tasks",
|
||||||
label: "定时任务管理",
|
label: "定时任务管理",
|
||||||
@@ -260,12 +262,12 @@ export const sidebarMenuItems: MenuItem[] = [
|
|||||||
label: "资源管理",
|
label: "资源管理",
|
||||||
icon: Database,
|
icon: Database,
|
||||||
children: [
|
children: [
|
||||||
{
|
// {
|
||||||
id: "resource-network-xwt",
|
// id: "resource-network-xwt",
|
||||||
label: "网络xwt资源管理",
|
// label: "网络xwt资源管理",
|
||||||
icon: Network,
|
// icon: Network,
|
||||||
path: "/page/strategy/resource-management/resource-network-actor",
|
// path: "/page/strategy/resource-management/resource-network-actor",
|
||||||
},
|
// },
|
||||||
{
|
{
|
||||||
id: "resource-knowledge-native",
|
id: "resource-knowledge-native",
|
||||||
label: "问答梳理",
|
label: "问答梳理",
|
||||||
@@ -278,12 +280,12 @@ export const sidebarMenuItems: MenuItem[] = [
|
|||||||
icon: Library,
|
icon: Library,
|
||||||
path: "/page/strategy/resource-management/resource-knowledge-base",
|
path: "/page/strategy/resource-management/resource-knowledge-base",
|
||||||
},
|
},
|
||||||
{
|
// {
|
||||||
id: "resource-evaluation-index",
|
// id: "resource-evaluation-index",
|
||||||
label: "作战资源管理",
|
// label: "作战资源管理",
|
||||||
icon: FileBox,
|
// icon: FileBox,
|
||||||
path: "/page/strategy/resource-management/resource-evaluation-index",
|
// path: "/page/strategy/resource-management/resource-evaluation-index",
|
||||||
},
|
// },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
@@ -298,9 +300,9 @@ export const defaultExpandedIds: ReadonlySet<string> = new Set([
|
|||||||
"comprehensive-management",
|
"comprehensive-management",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
/** 该菜单项自身或任意后代的 path 是否等于当前路由 */
|
/** 该菜单项自身或任意后代的 path 是否匹配当前路由(支持子路径,如 /knowledge/:id) */
|
||||||
export function menuItemContainsPath(item: MenuItem, pathname: string): boolean {
|
export function menuItemContainsPath(item: MenuItem, pathname: string): boolean {
|
||||||
if (item.path && item.path === pathname) {
|
if (item.path && (pathname === item.path || pathname.startsWith(item.path + "/"))) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return item.children?.some((child) => menuItemContainsPath(child, pathname)) ?? false;
|
return item.children?.some((child) => menuItemContainsPath(child, pathname)) ?? false;
|
||||||
@@ -308,7 +310,8 @@ export function menuItemContainsPath(item: MenuItem, pathname: string): boolean
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 在「一级 → 二级 → 三级」菜单中,取当前路由所在的「激活二级项」的三级子项。
|
* 在「一级 → 二级 → 三级」菜单中,取当前路由所在的「激活二级项」的三级子项。
|
||||||
* 浅色侧边栏据此在二级右侧展开三级列;无三级则返回空数组(侧边栏保持窄栏)。
|
* 浅色侧边栏据此在二级右侧展开三级列;深色顶栏据此渲染三级横条。
|
||||||
|
* 全量模式:三级仅一项时不返回(无选择必要,侧边栏保持窄栏 / 不渲染横条)。
|
||||||
*/
|
*/
|
||||||
export function getActiveThirdLevelItems(
|
export function getActiveThirdLevelItems(
|
||||||
items: MenuItem[],
|
items: MenuItem[],
|
||||||
@@ -320,16 +323,23 @@ export function getActiveThirdLevelItems(
|
|||||||
const activeSecond = secondary.find(
|
const activeSecond = secondary.find(
|
||||||
(item) => (item.children?.length ?? 0) > 0 && menuItemContainsPath(item, pathname),
|
(item) => (item.children?.length ?? 0) > 0 && menuItemContainsPath(item, pathname),
|
||||||
);
|
);
|
||||||
return activeSecond?.children ?? [];
|
const third = activeSecond?.children ?? [];
|
||||||
|
return third.length <= 1 ? [] : third;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 按 admin 角色过滤菜单:非 admin 不展示 ``adminOnly: true`` 的项及其子项 */
|
/** 按 admin 角色过滤菜单:非 admin 不展示 ``adminOnly: true`` 的项;``userPath`` 替换 ``path``。 */
|
||||||
export function filterMenuItemsByRole(items: MenuItem[], isAdmin: boolean): MenuItem[] {
|
export function filterMenuItemsByRole(items: MenuItem[], isAdmin: boolean): MenuItem[] {
|
||||||
return items
|
return items
|
||||||
.filter((item) => !item.adminOnly || isAdmin)
|
.filter((item) => !item.adminOnly || isAdmin)
|
||||||
.map((item) =>
|
.map((item) => {
|
||||||
item.children
|
const children = item.children
|
||||||
? { ...item, children: filterMenuItemsByRole(item.children, isAdmin) }
|
? filterMenuItemsByRole(item.children, isAdmin)
|
||||||
: item,
|
: undefined;
|
||||||
);
|
const base = children ? { ...item, children } : item;
|
||||||
|
if (!isAdmin && base.userPath) {
|
||||||
|
const { userPath: _userPath, ...rest } = base;
|
||||||
|
return { ...rest, path: base.userPath };
|
||||||
|
}
|
||||||
|
return base;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
@@ -650,7 +650,7 @@ export default function LightAppManagementPage() {
|
|||||||
onClick={() => setDialogOpen(false)}
|
onClick={() => setDialogOpen(false)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="lam-dialog relative z-[110] m-4 flex max-h-[90vh] w-full max-w-[580px] flex-col rounded-[5px] border border-[#E8E8E8] bg-white p-6 shadow-[0_8px_32px_rgba(34,34,34,0.16)]">
|
<div className="lam-dialog relative z-[110] m-4 flex max-h-[90vh] w-full max-w-[900px] flex-col rounded-[5px] border border-[#E8E8E8] bg-white p-6 shadow-[0_8px_32px_rgba(34,34,34,0.16)]">
|
||||||
<div className="lam-dialog-header flex flex-shrink-0 items-center justify-between border-b border-[#E8E8E8] pb-4">
|
<div className="lam-dialog-header flex flex-shrink-0 items-center justify-between border-b border-[#E8E8E8] pb-4">
|
||||||
<div className="flex items-center space-x-2.5">
|
<div className="flex items-center space-x-2.5">
|
||||||
<span className="lam-dialog-icon rounded-[5px] bg-rose-50 p-1.5 text-[#A8071A]">
|
<span className="lam-dialog-icon rounded-[5px] bg-rose-50 p-1.5 text-[#A8071A]">
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ import ChatPage from "./ChatPage";
|
|||||||
import ChatsPage from "./ChatsPage";
|
import ChatsPage from "./ChatsPage";
|
||||||
import AdminUserLeaderboardPage from "./AdminUserLeaderboardPage";
|
import AdminUserLeaderboardPage from "./AdminUserLeaderboardPage";
|
||||||
import AdminUserLeaderboardSettingsPage from "./AdminUserLeaderboardSettingsPage";
|
import AdminUserLeaderboardSettingsPage from "./AdminUserLeaderboardSettingsPage";
|
||||||
|
import PublicLeaderboardPage from "./PublicLeaderboardPage";
|
||||||
import LightAppIframeView from "./LightAppIframeView";
|
import LightAppIframeView from "./LightAppIframeView";
|
||||||
import LightAppListPage from "./LightAppListPage";
|
import LightAppListPage from "./LightAppListPage";
|
||||||
import LightAppManagementPage from "./LightAppManagementPage";
|
import LightAppManagementPage from "./LightAppManagementPage";
|
||||||
@@ -253,6 +254,7 @@ export default function StrategyRoutes() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
<Route path="user-leaderboard" element={<PublicLeaderboardPage />} />
|
||||||
<Route
|
<Route
|
||||||
path="admin/user-leaderboard"
|
path="admin/user-leaderboard"
|
||||||
element={
|
element={
|
||||||
|
|||||||
@@ -70,6 +70,7 @@ export function WorkspaceLayout({
|
|||||||
workspaceSidebarHidden = false,
|
workspaceSidebarHidden = false,
|
||||||
headerHidden = false,
|
headerHidden = false,
|
||||||
compactWorkspaceSidebar = false,
|
compactWorkspaceSidebar = false,
|
||||||
|
defaultHistorySidebarOpen,
|
||||||
wrapperOverflowHidden = false,
|
wrapperOverflowHidden = false,
|
||||||
insetOverflowHidden = false,
|
insetOverflowHidden = false,
|
||||||
pageSidebarDark = false,
|
pageSidebarDark = false,
|
||||||
@@ -80,6 +81,8 @@ export function WorkspaceLayout({
|
|||||||
workspaceSidebarHidden?: boolean;
|
workspaceSidebarHidden?: boolean;
|
||||||
headerHidden?: boolean;
|
headerHidden?: boolean;
|
||||||
compactWorkspaceSidebar?: boolean;
|
compactWorkspaceSidebar?: boolean;
|
||||||
|
/** 全量模式下通用问答等页:聊天记录侧栏默认展开;未指定时紧凑模式仍默认收起。 */
|
||||||
|
defaultHistorySidebarOpen?: boolean;
|
||||||
wrapperOverflowHidden?: boolean;
|
wrapperOverflowHidden?: boolean;
|
||||||
insetOverflowHidden?: boolean;
|
insetOverflowHidden?: boolean;
|
||||||
pageSidebarDark?: boolean;
|
pageSidebarDark?: boolean;
|
||||||
@@ -110,8 +113,7 @@ export function WorkspaceLayout({
|
|||||||
}
|
}
|
||||||
key={String(compactWorkspaceSidebar)}
|
key={String(compactWorkspaceSidebar)}
|
||||||
data-page-sidebar-collapsed={pageSidebarCollapsed}
|
data-page-sidebar-collapsed={pageSidebarCollapsed}
|
||||||
// 紧凑模式下历史侧栏默认收起(聊天区更宽),由聊天页右上角图标钮展开
|
defaultOpen={defaultHistorySidebarOpen ?? !compactWorkspaceSidebar}
|
||||||
defaultOpen={!compactWorkspaceSidebar}
|
|
||||||
>
|
>
|
||||||
{!workspaceSidebarHidden && (
|
{!workspaceSidebarHidden && (
|
||||||
<WorkspaceSidebar compactChrome={compactWorkspaceSidebar} />
|
<WorkspaceSidebar compactChrome={compactWorkspaceSidebar} />
|
||||||
@@ -153,7 +155,7 @@ function NewChatEntryRoute({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<WorkspaceLayout {...layoutProps}>
|
<WorkspaceLayout {...layoutProps} defaultHistorySidebarOpen>
|
||||||
<ChatRuntime>
|
<ChatRuntime>
|
||||||
<ChatPage />
|
<ChatPage />
|
||||||
</ChatRuntime>
|
</ChatRuntime>
|
||||||
@@ -201,7 +203,7 @@ export default function WorkspaceRoutes({
|
|||||||
<Route
|
<Route
|
||||||
path="chats/:thread_id"
|
path="chats/:thread_id"
|
||||||
element={
|
element={
|
||||||
<WorkspaceLayout {...layoutProps}>
|
<WorkspaceLayout {...layoutProps} defaultHistorySidebarOpen>
|
||||||
<ChatRuntime>
|
<ChatRuntime>
|
||||||
<ChatPage />
|
<ChatPage />
|
||||||
</ChatRuntime>
|
</ChatRuntime>
|
||||||
|
|||||||
@@ -8,7 +8,10 @@ import { NotificationDialog } from "@/components/workspace/notification-dialog";
|
|||||||
import { getStoredAuth } from "@/core/auth";
|
import { getStoredAuth } from "@/core/auth";
|
||||||
import { isWujieEnv } from "@/core/embed/wujie";
|
import { isWujieEnv } from "@/core/embed/wujie";
|
||||||
import { useUnreadCount } from "@/core/notifications";
|
import { useUnreadCount } from "@/core/notifications";
|
||||||
import type { MenuItem } from "@/core/page-layout/sidebar-menu";
|
import {
|
||||||
|
getActiveThirdLevelItems,
|
||||||
|
type MenuItem,
|
||||||
|
} from "@/core/page-layout/sidebar-menu";
|
||||||
import { useSidebarMenuItems } from "@/core/page-layout/use-sidebar-menu";
|
import { useSidebarMenuItems } from "@/core/page-layout/use-sidebar-menu";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
@@ -145,7 +148,7 @@ const Header = ({ collapsed }: HeaderProps) => {
|
|||||||
const showLightTopNav = !isDark && !isWujieEnv();
|
const showLightTopNav = !isDark && !isWujieEnv();
|
||||||
|
|
||||||
const hasActiveChild = (item: MenuItem): boolean => {
|
const hasActiveChild = (item: MenuItem): boolean => {
|
||||||
if (item.path && pathname === item.path) {
|
if (item.path && (pathname === item.path || pathname.startsWith(item.path + "/"))) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -170,9 +173,7 @@ const Header = ({ collapsed }: HeaderProps) => {
|
|||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
const activeTopItem = visibleMenuItems.find(hasActiveChild);
|
const thirdSubNavItems = getActiveThirdLevelItems(visibleMenuItems, pathname);
|
||||||
const activeSecondItem = activeTopItem?.children?.find(hasActiveChild);
|
|
||||||
const thirdSubNavItems = activeSecondItem?.children ?? [];
|
|
||||||
|
|
||||||
const leftMenuItems = visibleMenuItems.filter((i) => LEFT_MENU_IDS.includes(i.id));
|
const leftMenuItems = visibleMenuItems.filter((i) => LEFT_MENU_IDS.includes(i.id));
|
||||||
const rightMenuItems = visibleMenuItems.filter((i) => RIGHT_MENU_IDS.includes(i.id));
|
const rightMenuItems = visibleMenuItems.filter((i) => RIGHT_MENU_IDS.includes(i.id));
|
||||||
@@ -185,7 +186,7 @@ const Header = ({ collapsed }: HeaderProps) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const renderTopItem = (item: MenuItem) => {
|
const renderTopItem = (item: MenuItem) => {
|
||||||
const isSelected = item.path ? pathname === item.path : hasActiveChild(item);
|
const isSelected = item.path ? (pathname === item.path || pathname.startsWith(item.path + "/")) : hasActiveChild(item);
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
key={item.id}
|
key={item.id}
|
||||||
@@ -199,7 +200,7 @@ const Header = ({ collapsed }: HeaderProps) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const renderLightTopItem = (item: MenuItem) => {
|
const renderLightTopItem = (item: MenuItem) => {
|
||||||
const isSelected = item.path ? pathname === item.path : hasActiveChild(item);
|
const isSelected = item.path ? (pathname === item.path || pathname.startsWith(item.path + "/")) : hasActiveChild(item);
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
key={item.id}
|
key={item.id}
|
||||||
@@ -310,7 +311,7 @@ const Header = ({ collapsed }: HeaderProps) => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 中:CM助手(落在背景图发光基座上,纯文字无图标) */}
|
{/* 中:CM助手(落在背景图发光基座上,纯文字无图标) */}
|
||||||
<div className="header-center-title">CM助手</div>
|
<div className="header-center-title">CM业务中心</div>
|
||||||
|
|
||||||
{/* 右分区(flex-1):菜单靠左贴近中间基座,操作区靠最右 */}
|
{/* 右分区(flex-1):菜单靠左贴近中间基座,操作区靠最右 */}
|
||||||
<div className="flex h-full min-w-0 flex-1 items-center justify-between">
|
<div className="flex h-full min-w-0 flex-1 items-center justify-between">
|
||||||
@@ -336,7 +337,7 @@ const Header = ({ collapsed }: HeaderProps) => {
|
|||||||
<FileText className="h-4 w-4 text-white" />
|
<FileText className="h-4 w-4 text-white" />
|
||||||
</div>
|
</div>
|
||||||
<span className="whitespace-nowrap text-base font-semibold text-white">
|
<span className="whitespace-nowrap text-base font-semibold text-white">
|
||||||
CM助手
|
CM业务中心
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<nav className="light-topnav" aria-label="一级菜单">
|
<nav className="light-topnav" aria-label="一级菜单">
|
||||||
@@ -363,7 +364,7 @@ const Header = ({ collapsed }: HeaderProps) => {
|
|||||||
</div>
|
</div>
|
||||||
{!collapsed && (
|
{!collapsed && (
|
||||||
<span className="text-sm font-semibold text-white">
|
<span className="text-sm font-semibold text-white">
|
||||||
CM助手
|
CM业务中心
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -379,7 +380,7 @@ const Header = ({ collapsed }: HeaderProps) => {
|
|||||||
<nav className="third-submenu-bar" aria-label="三级菜单">
|
<nav className="third-submenu-bar" aria-label="三级菜单">
|
||||||
<div className="third-submenu-list">
|
<div className="third-submenu-list">
|
||||||
{thirdSubNavItems.map((item) => {
|
{thirdSubNavItems.map((item) => {
|
||||||
const isSelected = item.path ? pathname === item.path : hasActiveChild(item);
|
const isSelected = item.path ? (pathname === item.path || pathname.startsWith(item.path + "/")) : hasActiveChild(item);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
|
|||||||
Reference in new issue
Block a user