- {visibleMenuItems.map((item) => (
-
- >
+
);
}
diff --git a/frontend-web/src/components/page-sidebar.tsx b/frontend-web/src/components/page-sidebar.tsx
index 11dec345..10512957 100644
--- a/frontend-web/src/components/page-sidebar.tsx
+++ b/frontend-web/src/components/page-sidebar.tsx
@@ -1,40 +1,14 @@
import { useEffect, useState } from "react";
-import type { ElementType } from "react";
import { useNavigate, useLocation } from "react-router-dom";
import {
Bell,
- Bot,
- Brain,
- CalendarClock,
ChevronLeft,
ChevronRight,
ChevronsUpDown,
- CircleHelp,
- Database,
- FileBox,
- FileText,
Folder,
FolderOpen,
- Gauge,
- Grid3X3,
- HelpCircle,
- History,
- Layout,
- Library,
- List,
- ListChecks,
- ListTodo,
- MessageSquareCode,
- MessageSquarePlus,
- ShieldCheck,
- Network,
- NotebookPen,
- Plus,
Settings,
- Sparkles,
- Target,
User,
- Users,
Wrench,
} from "lucide-react";
@@ -50,14 +24,12 @@ import { useTheme } from "@/contexts/ThemeContext";
import { getStoredAuth } from "@/core/auth";
import { useI18n } from "@/core/i18n/hooks";
import { useUnreadCount } from "@/core/notifications";
-type MenuItem = {
- id: string;
- label: string;
- icon: ElementType;
- path?: string;
- adminOnly?: boolean;
- children?: MenuItem[];
-};
+import {
+ defaultExpandedIds,
+ filterMenuItemsByRole,
+ sidebarMenuItems,
+ type MenuItem,
+} from "@/core/page-layout/sidebar-menu";
type PageSidebarProps = {
collapsed: boolean;
@@ -66,272 +38,6 @@ type PageSidebarProps = {
type SettingsSection = "account" | "appearance" | "memory" | "tools" | "about";
-const menuItems: MenuItem[] = [
- {
- id: "qa-center",
- label: "问答中心",
- icon: HelpCircle,
- children: [
- { id: "general-qa", label: "通用问答", icon: CircleHelp, path: "/page/workspace/chats/new" },
- { id: "expert-qa", label: "专家问答", icon: Brain, path: "/page/strategy/qa/expert-qa" },
- // 「圆桌规划」入口暂时隐藏 —— 功能仍在 /page/roundtable/planning,只是不在侧边栏出现。
- // 需要重新开放时取消注释即可。
- // {
- // id: "qa-roundtable",
- // label: "圆桌规划",
- // icon: Bot,
- // path: "/page/roundtable/planning",
- // },
- { id: "qa-zzjc", label: "zzjc支持", icon: Bot },
- { id: "qa-zzxd", label: "zzXD应用", icon: Layout },
- { id: "qa-research", label: "课题研究", icon: FileText, path: "/page/canvas/ai-writing" },
- ],
- },
- {
- id: "light-app-center",
- label: "轻应用",
- icon: Grid3X3,
- children: [
- {
- id: "light-app-note-management",
- label: "笔记管理",
- icon: NotebookPen,
- children: [
- {
- id: "light-app-new-note",
- label: "新建笔记",
- icon: Plus,
- path: "/page/strategy/light-app/new-note",
- },
- {
- id: "light-app-note-list",
- label: "笔记列表",
- icon: List,
- path: "/page/strategy/light-app/notes",
- },
- {
- id: "light-app-note-qa",
- label: "笔记问答",
- icon: MessageSquareCode,
- path: "/page/strategy/light-app/note-qa",
- },
- ],
- },
- { id: "light-app-square", label: "应用广场", icon: Layout },
- { id: "light-app-records", label: "运行记录", icon: History },
- ],
- },
- {
- id: "task-management",
- label: "任务管理",
- icon: Target,
- children: [
- {
- id: "xk-center-task",
- label: "XK中任务",
- icon: Folder,
- children: [
- {
- id: "xk-task-jc",
- label: "任务JC",
- icon: ListTodo,
- path: "/page/strategy/task-management/xk-task-jc",
- },
- {
- id: "xk-task-dispatch",
- label: "任务调度",
- icon: ListTodo,
- path: "/page/strategy/task-management/xk-task-dispatch",
- },
- ],
- },
- {
- id: "coh-center-task",
- label: "COH中任务",
- icon: Folder,
- children: [
- {
- id: "coh-task-3qfx",
- label: "3QFX",
- icon: ListTodo,
- path: "/page/strategy/task-management/plan-task-sentiment",
- },
- {
- id: "coh-task-fx",
- label: "任务FX",
- icon: ListTodo,
- path: "/page/strategy/task-management/plan-task/plan-task-fx",
- },
- {
- id: "coh-task-xdfx",
- label: "XDFX",
- icon: ListTodo,
- path: "/page/strategy/task-management/plan-task/plan-task-action",
- },
- ],
- },
- {
- id: "pg-center-task",
- label: "PG中任务",
- icon: Folder,
- children: [
- {
- id: "pg-task-pg",
- label: "任务PG",
- icon: ListTodo,
- path: "/page/strategy/task-management/effect-task-pg",
- },
- {
- id: "pg-task-xdpg",
- label: "XDPG",
- icon: ListTodo,
- path: "/page/strategy/task-management/effect-action-pg",
- },
- {
- id: "pg-task-info-pg",
- label: "信息PG",
- icon: ListTodo,
- path: "/page/strategy/task-management/effect-info-pg",
- },
- ],
- },
- {
- id: "ty-center-task",
- label: "TY中任务",
- icon: Folder,
- children: [
- {
- id: "ty-task-xdty",
- label: "XDTY任务",
- icon: ListTodo,
- path: "/page/strategy/task-management/mnty-task-mechanism",
- },
- {
- id: "ty-task-plan",
- label: "方案TY任务",
- icon: ListTodo,
- path: "/page/strategy/task-management/mnty-task-plan",
- },
- {
- id: "ty-task-xxty",
- label: "XXTY任务",
- icon: ListTodo,
- path: "/page/strategy/task-management/mnty-task-zsty",
- },
- ],
- },
- ],
- },
- {
- id: "comprehensive-management",
- label: "综合管理",
- icon: Settings,
- children: [
- { id: "mgmt-agents", label: "智能体管理", icon: Bot, path: "/page/strategy/agents" },
- { id: "mgmt-skills", label: "技能管理", icon: Sparkles, path: "/page/strategy/skills" },
- { id: "mgmt-memory", label: "记忆管理", icon: Brain, path: "/page/strategy/memory" },
- {
- id: "mgmt-tasks",
- label: "定时任务",
- icon: ListChecks,
- path: "/page/strategy/scheduled/tasks",
- },
- {
- id: "mgmt-recommended-questions",
- label: "推荐问题配置",
- icon: MessageSquarePlus,
- path: "/page/strategy/settings/recommended-questions",
- adminOnly: true,
- },
- {
- id: "mgmt-prompt-prefix",
- label: "问答前缀配置",
- icon: MessageSquareCode,
- path: "/page/strategy/settings/prompt-prefix",
- adminOnly: true,
- },
- {
- id: "mgmt-private-square",
- label: "私密广场配置",
- icon: ShieldCheck,
- path: "/page/strategy/settings/private-square",
- adminOnly: true,
- },
- {
- id: "mgmt-llm-metrics",
- label: "大模型调用统计",
- icon: Gauge,
- path: "/page/strategy/settings/llm-metrics",
- adminOnly: true,
- },
- {
- id: "mgmt-users",
- label: "用户管理",
- icon: Users,
- path: "/page/strategy/admin/users",
- adminOnly: true,
- },
- {
- id: "mgmt-admin-scheduled-tasks",
- label: "定时任务管理",
- icon: CalendarClock,
- path: "/page/strategy/admin/scheduled-tasks",
- adminOnly: true,
- },
- {
- id: "mgmt-user-activity",
- label: "用户活动审计",
- icon: History,
- path: "/page/strategy/admin/user-activity",
- adminOnly: true,
- },
- ],
- },
- {
- id: "resource-management",
- label: "资源管理",
- icon: Database,
- children: [
- {
- id: "resource-network-xwt",
- label: "网络xwt资源管理",
- icon: Network,
- path: "/page/strategy/resource-management/resource-network-actor",
- },
- {
- id: "resource-knowledge-base",
- label: "知识库资源管理",
- icon: Library,
- path: "/page/strategy/resource-management/resource-knowledge-base",
- },
- {
- id: "resource-evaluation-index",
- label: "作战资源管理",
- icon: FileBox,
- path: "/page/strategy/resource-management/resource-evaluation-index",
- },
- ],
- },
-];
-
-const defaultExpandedIds = new Set([
- "qa-center",
- "light-app-center",
- "light-app-note-management",
- "task-management",
- "xk-center-task",
-]);
-
-function filterMenuItemsByRole(items: MenuItem[], isAdmin: boolean): MenuItem[] {
- return items
- .filter((item) => !item.adminOnly || isAdmin)
- .map((item) =>
- item.children
- ? { ...item, children: filterMenuItemsByRole(item.children, isAdmin) }
- : item,
- );
-}
-
export function PageSidebar({
collapsed,
onToggleCollapsed,
@@ -339,10 +45,11 @@ export function PageSidebar({
const { theme } = useTheme();
const isDark = theme === "dark";
const isAdmin = getStoredAuth()?.system_role === "admin";
- const visibleMenuItems = filterMenuItemsByRole(menuItems, !!isAdmin);
+ const visibleMenuItems = filterMenuItemsByRole(sidebarMenuItems, !!isAdmin);
const { t } = useI18n();
const { data: unreadCount = 0 } = useUnreadCount();
- const [expandedIds, setExpandedIds] = useState
>(defaultExpandedIds);
+ // defaultExpandedIds 是共享的 ReadonlySet,复制一份给 useState(避免外部突变)
+ const [expandedIds, setExpandedIds] = useState>(() => new Set(defaultExpandedIds));
const [settingsOpen, setSettingsOpen] = useState(false);
const [settingsDefaultSection, setSettingsDefaultSection] =
useState("appearance");
diff --git a/frontend-web/src/components/ui/card.tsx b/frontend-web/src/components/ui/card.tsx
index eb693c16..8a613603 100644
--- a/frontend-web/src/components/ui/card.tsx
+++ b/frontend-web/src/components/ui/card.tsx
@@ -7,7 +7,7 @@ function Card({ className, ...props }: React.ComponentProps<"div">) {
) {
return (
);
@@ -65,7 +65,7 @@ function CardContent({ className, ...props }: React.ComponentProps<"div">) {
return (
);
diff --git a/frontend-web/src/components/ui/input-group.tsx b/frontend-web/src/components/ui/input-group.tsx
index bb535dbb..ec7272b4 100644
--- a/frontend-web/src/components/ui/input-group.tsx
+++ b/frontend-web/src/components/ui/input-group.tsx
@@ -14,7 +14,7 @@ function InputGroup({ className, ...props }: React.ComponentProps<"div">) {
data-slot="input-group"
role="group"
className={cn(
- "group/input-group border-input/50 dark:bg-background/80 relative flex w-full items-center rounded-md border bg-white/80 shadow-xs transition-[color,box-shadow] outline-none",
+ "group/input-group relative flex w-full items-center rounded-md border border-input/50 bg-white/80 shadow-xs transition-[color,box-shadow] outline-none dark:border-[#1B477E] dark:bg-[#022248]",
"h-9 min-w-0 has-[>textarea]:h-auto",
// Variants based on alignment.
diff --git a/frontend-web/src/components/ui/input.tsx b/frontend-web/src/components/ui/input.tsx
index 481e6788..6537bb0f 100644
--- a/frontend-web/src/components/ui/input.tsx
+++ b/frontend-web/src/components/ui/input.tsx
@@ -8,9 +8,12 @@ function Input({ className, type, ...props }: React.ComponentProps<"input">) {
type={type}
data-slot="input"
className={cn(
- "file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 border-input h-9 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
- "focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]",
- "aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
+ "h-9 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base outline-none transition-[color,box-shadow] md:text-sm",
+ "border-input placeholder:text-muted-foreground",
+ "dark:border-[#1B477E] dark:bg-[#022248] dark:text-[#AACEFF] dark:placeholder:text-[#566B7F] rounded-[4px] text-[16px]",
+ "file:text-foreground selection:bg-primary selection:text-primary-foreground file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50",
+ "focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] dark:focus-visible:border-[#3b82f6] dark:focus-visible:ring-[#3b82f6]/30",
+ "aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40",
className,
)}
{...props}
diff --git a/frontend-web/src/components/ui/tabs.tsx b/frontend-web/src/components/ui/tabs.tsx
index 41a53cc7..9bbb70e2 100644
--- a/frontend-web/src/components/ui/tabs.tsx
+++ b/frontend-web/src/components/ui/tabs.tsx
@@ -30,7 +30,8 @@ const tabsListVariants = cva(
{
variants: {
variant: {
- default: "bg-muted",
+ default:
+ "bg-muted dark:group-data-[orientation=horizontal]/tabs:h-8! dark:items-stretch dark:gap-0 dark:overflow-hidden dark:rounded-[2px] dark:border dark:border-[#55A6FE] dark:bg-[#022248] dark:p-0",
line: "gap-1 bg-transparent",
},
},
@@ -40,60 +41,65 @@ const tabsListVariants = cva(
},
);
-const TabsList = React.forwardRef<
- React.ElementRef
,
- React.ComponentPropsWithoutRef &
- VariantProps
->(({ className, variant = "default", ...props }, ref) => {
- return (
-
- );
-});
-
-TabsList.displayName = TabsPrimitive.List.displayName;
+const TabsList = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef &
+ VariantProps
+>(({ className, variant = "default", ...props }, ref) => {
+ return (
+
+ );
+});
-const TabsTrigger = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, ...props }, ref) => {
- return (
-
- );
-});
-
-TabsTrigger.displayName = TabsPrimitive.Trigger.displayName;
+TabsList.displayName = TabsPrimitive.List.displayName;
-const TabsContent = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, ...props }, ref) => {
- return (
-
- );
-});
-
-TabsContent.displayName = TabsPrimitive.Content.displayName;
+const TabsTrigger = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => {
+ return (
+
+ );
+});
+
+TabsTrigger.displayName = TabsPrimitive.Trigger.displayName;
+
+const TabsContent = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => {
+ return (
+
+ );
+});
+
+TabsContent.displayName = TabsPrimitive.Content.displayName;
export { Tabs, TabsList, TabsTrigger, TabsContent, tabsListVariants };
diff --git a/frontend-web/src/components/ui/textarea.tsx b/frontend-web/src/components/ui/textarea.tsx
index d55b5a4b..5bdc6ef7 100644
--- a/frontend-web/src/components/ui/textarea.tsx
+++ b/frontend-web/src/components/ui/textarea.tsx
@@ -7,7 +7,9 @@ function Textarea({ className, ...props }: React.ComponentProps<"textarea">) {