Merge branch 'dhk-1' into 'master'

AI 写作四阶段内置智能体 Phase 1-4 落地

See merge request knowledge-base-bj/deerflow-server!116
This commit is contained in:
董宏凯 committed 2026-06-12 10:20:27 +00:00
commit c2f6d4fd6f
94 files changed
+6935 -1132

No files matched your search

+152 -1
View File
@@ -320,4 +320,155 @@
max-width: 64px;
opacity: 1;
transform: translateX(0);
}
}
.sidebar-contnet--top-submenu .item-nav:hover::before,
.sidebar-contnet--top-submenu .item-nav-active::before,
.sidebar-contnet--top-submenu .item-nav-hovered::before {
display: none;
}
.top-submenu-bar {
position: relative;
z-index: 35;
height: 64px;
min-width: 0;
flex: 1 1 auto;
align-self: stretch;
margin-left: 34px;
background: transparent;
border: 0;
box-shadow: none;
overflow-x: auto;
overflow-y: hidden;
}
.top-submenu-list {
min-width: max-content;
height: 64px;
display: flex;
align-items: stretch;
gap: 0;
padding: 0;
}
.top-submenu-item {
position: relative;
min-width: 122px;
height: 64px;
display: inline-flex;
align-items: center;
justify-content: center;
border: 0;
padding: 0 18px;
background: transparent;
color: rgba(224, 242, 255, 0.9);
font-family: Noto Sans S Chinese;
font-size: 16px;
line-height: 1;
font-weight: 500;
letter-spacing: 0;
white-space: nowrap;
cursor: pointer;
overflow: hidden;
transition: color 0.18s ease, text-shadow 0.18s ease, background 0.18s ease;
}
.top-submenu-item:hover,
.top-submenu-item--active {
color: #ffffff;
text-shadow: 0 0 10px rgba(91, 210, 255, 0.45);
background:
linear-gradient(135deg, rgba(15, 84, 169, 0.52) 0%, rgba(5, 43, 103, 0.34) 76%),
linear-gradient(180deg, rgba(39, 126, 230, 0.2) 0%, rgba(0, 50, 122, 0.26) 100%);
box-shadow: inset 0 1px 0 rgba(98, 180, 255, 0.16), inset 0 -18px 22px rgba(0, 31, 79, 0.24);
}
.top-submenu-item--active::before {
content: '';
position: absolute;
left: 0;
right: 0;
bottom: 0;
height: 2px;
background: linear-gradient(90deg, rgba(59, 162, 255, 0) 0%, #35d7ff 50%, rgba(59, 162, 255, 0) 100%);
box-shadow: 0 0 12px rgba(53, 215, 255, 0.9), 0 -6px 18px rgba(33, 143, 255, 0.36);
}
.top-submenu-item--active::after {
content: '';
position: absolute;
left: 16px;
right: 16px;
bottom: 0;
height: 16px;
background: radial-gradient(ellipse at bottom, rgba(37, 189, 255, 0.34) 0%, rgba(37, 189, 255, 0) 68%);
pointer-events: none;
}
.third-submenu-bar {
position: relative;
z-index: 34;
height: 42px;
margin-left: 118px;
flex-shrink: 0;
background:
linear-gradient(180deg, rgba(4, 34, 78, 0.96) 0%, rgba(2, 24, 58, 0.96) 100%),
radial-gradient(ellipse at top left, rgba(45, 149, 255, 0.18) 0%, rgba(45, 149, 255, 0) 42%);
border-top: 1px solid rgba(79, 154, 255, 0.18);
border-bottom: 1px solid rgba(43, 143, 254, 0.26);
box-shadow: inset 0 1px 0 rgba(115, 194, 255, 0.08), inset 0 -10px 18px rgba(0, 26, 70, 0.22);
overflow-x: auto;
overflow-y: hidden;
}
.third-submenu-list {
min-width: max-content;
height: 100%;
display: flex;
align-items: center;
gap: 10px;
padding: 0 24px;
}
.third-submenu-item {
position: relative;
height: 28px;
min-width: 86px;
display: inline-flex;
align-items: center;
justify-content: center;
border: 1px solid rgba(90, 169, 255, 0.2);
border-radius: 2px;
padding: 0 16px;
background: rgba(2, 28, 65, 0.36);
color: rgba(221, 242, 255, 0.78);
font-family: Noto Sans S Chinese;
font-size: 14px;
line-height: 1;
font-weight: 500;
letter-spacing: 0;
white-space: nowrap;
cursor: pointer;
transition: color 0.18s ease, border-color 0.18s ease, background 0.18s ease, box-shadow 0.18s ease;
}
.third-submenu-item:hover,
.third-submenu-item--active {
color: #ffffff;
border-color: rgba(82, 205, 255, 0.55);
background: linear-gradient(180deg, rgba(14, 84, 165, 0.54) 0%, rgba(4, 45, 104, 0.58) 100%);
box-shadow: inset 0 0 12px rgba(0, 185, 255, 0.14), 0 0 10px rgba(35, 167, 255, 0.14);
text-shadow: 0 0 8px rgba(87, 215, 255, 0.34);
}
.third-submenu-item--active::after {
content: '';
position: absolute;
left: 12px;
right: 12px;
bottom: -1px;
height: 2px;
background: linear-gradient(90deg, rgba(62, 178, 255, 0) 0%, #4ee6ff 50%, rgba(62, 178, 255, 0) 100%);
box-shadow: 0 0 10px rgba(78, 230, 255, 0.72);
}
@@ -1,14 +1,14 @@
import { useEffect, useMemo, useRef, useState } from "react";
import { useEffect, useRef, useState } from "react";
import { useNavigate, useLocation } from "react-router-dom";
import { PanelLeftOpen, PanelLeftClose } from "lucide-react";
import { getStoredAuth } from "@/core/auth";
import { isWujieEnv } from "@/core/embed/wujie";
import {
defaultExpandedIds,
filterMenuItemsByRole,
sidebarMenuItems,
type MenuItem,
} from "@/core/page-layout/sidebar-menu";
import { useSidebarMenuItems } from "@/core/page-layout/use-sidebar-menu";
import './page-sdebar.css'
type PageSidebarDarkProps = {
@@ -19,10 +19,8 @@ type PageSidebarDarkProps = {
export function PageSidebarDark({ collapsed, onToggleCollapsed }: PageSidebarDarkProps) {
const isAdmin = getStoredAuth()?.system_role === "admin";
const visibleMenuItems = useMemo(
() => filterMenuItemsByRole(sidebarMenuItems, !!isAdmin),
[isAdmin],
);
const visibleMenuItems = useSidebarMenuItems(!!isAdmin);
const inWujieEnv = isWujieEnv();
// defaultExpandedIds 是共享的 ReadonlySet,复制一份给 useState(避免外部突变)
const [expandedIds, setExpandedIds] = useState<Set<string>>(() => new Set(defaultExpandedIds));
const navigate = useNavigate();
@@ -163,6 +161,7 @@ export function PageSidebarDark({ collapsed, onToggleCollapsed }: PageSidebarDar
})}
</div>
);
// collapsed 状态:sidebar 主体完全消失(包括占据的宽度),只渲染左下角浮动入口
if (collapsed) {
return (
@@ -180,16 +179,16 @@ export function PageSidebarDark({ collapsed, onToggleCollapsed }: PageSidebarDar
}
return (
<aside className="sidebar-contnet">
<aside className={`sidebar-contnet ${inWujieEnv ? "" : "sidebar-contnet--top-submenu"}`}>
<div className="sidebar-contents">
{visibleMenuItems.map((item) => (
<div
key={item.id}
className="item-nav-group"
onMouseEnter={() => handleNavHoverEnter(item.id)}
onMouseLeave={scheduleNavPanelClose}
onMouseEnter={inWujieEnv ? () => handleNavHoverEnter(item.id) : undefined}
onMouseLeave={inWujieEnv ? scheduleNavPanelClose : undefined}
>
{item.children?.length ? (
{inWujieEnv && item.children?.length ? (
<div
className={`item-nav-content ${hoveredTopId === item.id ? "item-nav-content--visible" : ""}`}
>
@@ -204,7 +203,7 @@ export function PageSidebarDark({ collapsed, onToggleCollapsed }: PageSidebarDar
>
<span className="item-nav__label">{item.label}</span>
</button>
{item.children?.length && hoveredTopId === item.id ? (
{inWujieEnv && item.children?.length && hoveredTopId === item.id ? (
<span className="item-nav-hover-bridge" aria-hidden="true" />
) : null}
</div>
+2 -3
View File
@@ -26,10 +26,9 @@ import { useI18n } from "@/core/i18n/hooks";
import { useUnreadCount } from "@/core/notifications";
import {
defaultExpandedIds,
filterMenuItemsByRole,
sidebarMenuItems,
type MenuItem,
} from "@/core/page-layout/sidebar-menu";
import { useSidebarMenuItems } from "@/core/page-layout/use-sidebar-menu";
type PageSidebarProps = {
collapsed: boolean;
@@ -45,7 +44,7 @@ export function PageSidebar({
const { theme } = useTheme();
const isDark = theme === "dark";
const isAdmin = getStoredAuth()?.system_role === "admin";
const visibleMenuItems = filterMenuItemsByRole(sidebarMenuItems, !!isAdmin);
const visibleMenuItems = useSidebarMenuItems(!!isAdmin);
const { t } = useI18n();
const { data: unreadCount = 0 } = useUnreadCount();
// defaultExpandedIds 是共享的 ReadonlySet,复制一份给 useState(避免外部突变)
@@ -23,7 +23,7 @@ function classifySpeed(tps: number): { color: string; label: string } {
return { color: "#ef4444", label: "缓慢" };
}
function formatDuration(ms: number): string {
export function formatDuration(ms: number): string {
if (ms < 1000) return `${Math.round(ms)} ms`;
if (ms < 60_000) return `${(ms / 1000).toFixed(1)} s`;
const mins = Math.floor(ms / 60_000);
@@ -31,7 +31,7 @@ function formatDuration(ms: number): string {
return `${mins}m ${secs}s`;
}
function SpeedPill({ metric }: { metric: MessageMetric }) {
export function SpeedPill({ metric }: { metric: MessageMetric }) {
const fill = Math.max(0, Math.min(1, metric.tokensPerSec / FULL_SCALE_TOKENS_PER_SEC));
const { color, label } = classifySpeed(metric.tokensPerSec);
const display =