mirror of
https://gitee.com/wanwujie/deer-flow
synced 2026-04-23 22:24:46 +08:00
feat: add main menu
This commit is contained in:
@@ -25,6 +25,7 @@
|
|||||||
"@codemirror/language-data": "^6.5.2",
|
"@codemirror/language-data": "^6.5.2",
|
||||||
"@langchain/core": "^1.1.15",
|
"@langchain/core": "^1.1.15",
|
||||||
"@langchain/langgraph-sdk": "^1.5.3",
|
"@langchain/langgraph-sdk": "^1.5.3",
|
||||||
|
"@radix-ui/react-avatar": "^1.1.11",
|
||||||
"@radix-ui/react-collapsible": "^1.1.12",
|
"@radix-ui/react-collapsible": "^1.1.12",
|
||||||
"@radix-ui/react-dialog": "^1.1.15",
|
"@radix-ui/react-dialog": "^1.1.15",
|
||||||
"@radix-ui/react-dropdown-menu": "^2.1.16",
|
"@radix-ui/react-dropdown-menu": "^2.1.16",
|
||||||
|
|||||||
45
frontend/pnpm-lock.yaml
generated
45
frontend/pnpm-lock.yaml
generated
@@ -35,6 +35,9 @@ importers:
|
|||||||
'@langchain/langgraph-sdk':
|
'@langchain/langgraph-sdk':
|
||||||
specifier: ^1.5.3
|
specifier: ^1.5.3
|
||||||
version: 1.5.3(@langchain/core@1.1.15(@opentelemetry/api@1.9.0))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
|
version: 1.5.3(@langchain/core@1.1.15(@opentelemetry/api@1.9.0))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
|
||||||
|
'@radix-ui/react-avatar':
|
||||||
|
specifier: ^1.1.11
|
||||||
|
version: 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.8))(@types/react@19.2.8)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
|
||||||
'@radix-ui/react-collapsible':
|
'@radix-ui/react-collapsible':
|
||||||
specifier: ^1.1.12
|
specifier: ^1.1.12
|
||||||
version: 1.1.12(@types/react-dom@19.2.3(@types/react@19.2.8))(@types/react@19.2.8)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
|
version: 1.1.12(@types/react-dom@19.2.3(@types/react@19.2.8))(@types/react@19.2.8)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
|
||||||
@@ -1011,6 +1014,19 @@ packages:
|
|||||||
'@types/react-dom':
|
'@types/react-dom':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
'@radix-ui/react-avatar@1.1.11':
|
||||||
|
resolution: {integrity: sha512-0Qk603AHGV28BOBO34p7IgD5m+V5Sg/YovfayABkoDDBM5d3NCx0Mp4gGrjzLGes1jV5eNOE1r3itqOR33VC6Q==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '*'
|
||||||
|
'@types/react-dom': '*'
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
'@types/react-dom':
|
||||||
|
optional: true
|
||||||
|
|
||||||
'@radix-ui/react-collapsible@1.1.12':
|
'@radix-ui/react-collapsible@1.1.12':
|
||||||
resolution: {integrity: sha512-Uu+mSh4agx2ib1uIGPP4/CKNULyajb3p92LsVXmH2EHVMTfZWpll88XJ0j4W0z3f8NK1eYl1+Mf/szHPmcHzyA==}
|
resolution: {integrity: sha512-Uu+mSh4agx2ib1uIGPP4/CKNULyajb3p92LsVXmH2EHVMTfZWpll88XJ0j4W0z3f8NK1eYl1+Mf/szHPmcHzyA==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -1405,6 +1421,15 @@ packages:
|
|||||||
'@types/react':
|
'@types/react':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
'@radix-ui/react-use-is-hydrated@0.1.0':
|
||||||
|
resolution: {integrity: sha512-U+UORVEq+cTnRIaostJv9AGdV3G6Y+zbVd+12e18jQ5A3c0xL03IhnHuiU4UV69wolOQp5GfR58NW/EgdQhwOA==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '*'
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
|
||||||
'@radix-ui/react-use-layout-effect@1.1.1':
|
'@radix-ui/react-use-layout-effect@1.1.1':
|
||||||
resolution: {integrity: sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==}
|
resolution: {integrity: sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -5963,6 +5988,19 @@ snapshots:
|
|||||||
'@types/react': 19.2.8
|
'@types/react': 19.2.8
|
||||||
'@types/react-dom': 19.2.3(@types/react@19.2.8)
|
'@types/react-dom': 19.2.3(@types/react@19.2.8)
|
||||||
|
|
||||||
|
'@radix-ui/react-avatar@1.1.11(@types/react-dom@19.2.3(@types/react@19.2.8))(@types/react@19.2.8)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)':
|
||||||
|
dependencies:
|
||||||
|
'@radix-ui/react-context': 1.1.3(@types/react@19.2.8)(react@19.2.3)
|
||||||
|
'@radix-ui/react-primitive': 2.1.4(@types/react-dom@19.2.3(@types/react@19.2.8))(@types/react@19.2.8)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
|
||||||
|
'@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.8)(react@19.2.3)
|
||||||
|
'@radix-ui/react-use-is-hydrated': 0.1.0(@types/react@19.2.8)(react@19.2.3)
|
||||||
|
'@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.8)(react@19.2.3)
|
||||||
|
react: 19.2.3
|
||||||
|
react-dom: 19.2.3(react@19.2.3)
|
||||||
|
optionalDependencies:
|
||||||
|
'@types/react': 19.2.8
|
||||||
|
'@types/react-dom': 19.2.3(@types/react@19.2.8)
|
||||||
|
|
||||||
'@radix-ui/react-collapsible@1.1.12(@types/react-dom@19.2.3(@types/react@19.2.8))(@types/react@19.2.8)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)':
|
'@radix-ui/react-collapsible@1.1.12(@types/react-dom@19.2.3(@types/react@19.2.8))(@types/react@19.2.8)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@radix-ui/primitive': 1.1.3
|
'@radix-ui/primitive': 1.1.3
|
||||||
@@ -6373,6 +6411,13 @@ snapshots:
|
|||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@types/react': 19.2.8
|
'@types/react': 19.2.8
|
||||||
|
|
||||||
|
'@radix-ui/react-use-is-hydrated@0.1.0(@types/react@19.2.8)(react@19.2.3)':
|
||||||
|
dependencies:
|
||||||
|
react: 19.2.3
|
||||||
|
use-sync-external-store: 1.6.0(react@19.2.3)
|
||||||
|
optionalDependencies:
|
||||||
|
'@types/react': 19.2.8
|
||||||
|
|
||||||
'@radix-ui/react-use-layout-effect@1.1.1(@types/react@19.2.8)(react@19.2.3)':
|
'@radix-ui/react-use-layout-effect@1.1.1(@types/react@19.2.8)(react@19.2.3)':
|
||||||
dependencies:
|
dependencies:
|
||||||
react: 19.2.3
|
react: 19.2.3
|
||||||
|
|||||||
53
frontend/src/components/ui/avatar.tsx
Normal file
53
frontend/src/components/ui/avatar.tsx
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import * as React from "react"
|
||||||
|
import * as AvatarPrimitive from "@radix-ui/react-avatar"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
function Avatar({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof AvatarPrimitive.Root>) {
|
||||||
|
return (
|
||||||
|
<AvatarPrimitive.Root
|
||||||
|
data-slot="avatar"
|
||||||
|
className={cn(
|
||||||
|
"relative flex size-8 shrink-0 overflow-hidden rounded-full",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function AvatarImage({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof AvatarPrimitive.Image>) {
|
||||||
|
return (
|
||||||
|
<AvatarPrimitive.Image
|
||||||
|
data-slot="avatar-image"
|
||||||
|
className={cn("aspect-square size-full", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function AvatarFallback({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof AvatarPrimitive.Fallback>) {
|
||||||
|
return (
|
||||||
|
<AvatarPrimitive.Fallback
|
||||||
|
data-slot="avatar-fallback"
|
||||||
|
className={cn(
|
||||||
|
"bg-muted flex size-full items-center justify-center rounded-full",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export { Avatar, AvatarImage, AvatarFallback }
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { MessagesSquare } from "lucide-react";
|
||||||
|
import Link from "next/link";
|
||||||
|
import { usePathname } from "next/navigation";
|
||||||
|
|
||||||
|
import {
|
||||||
|
SidebarGroup,
|
||||||
|
SidebarMenu,
|
||||||
|
SidebarMenuButton,
|
||||||
|
SidebarMenuItem,
|
||||||
|
} from "@/components/ui/sidebar";
|
||||||
|
import { useI18n } from "@/core/i18n/hooks";
|
||||||
|
|
||||||
|
export function WorkspaceNavChatList() {
|
||||||
|
const { t } = useI18n();
|
||||||
|
const pathname = usePathname();
|
||||||
|
return (
|
||||||
|
<SidebarGroup className="pt-1">
|
||||||
|
<SidebarMenu>
|
||||||
|
<SidebarMenuItem>
|
||||||
|
<SidebarMenuButton isActive={pathname === "/workspace/chats"} asChild>
|
||||||
|
<Link className="text-muted-foreground" href="/workspace/chats">
|
||||||
|
<MessagesSquare />
|
||||||
|
<span>{t.sidebar.chats}</span>
|
||||||
|
</Link>
|
||||||
|
</SidebarMenuButton>
|
||||||
|
</SidebarMenuItem>
|
||||||
|
</SidebarMenu>
|
||||||
|
</SidebarGroup>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,32 +1,101 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { MessagesSquare } from "lucide-react";
|
import {
|
||||||
import Link from "next/link";
|
BugIcon,
|
||||||
import { usePathname } from "next/navigation";
|
ChevronsUpDown,
|
||||||
|
InfoIcon,
|
||||||
|
MailIcon,
|
||||||
|
Settings2Icon,
|
||||||
|
SettingsIcon,
|
||||||
|
} from "lucide-react";
|
||||||
|
import { useState } from "react";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
SidebarGroup,
|
DropdownMenu,
|
||||||
|
DropdownMenuContent,
|
||||||
|
DropdownMenuGroup,
|
||||||
|
DropdownMenuItem,
|
||||||
|
DropdownMenuSeparator,
|
||||||
|
DropdownMenuTrigger,
|
||||||
|
} from "@/components/ui/dropdown-menu";
|
||||||
|
import {
|
||||||
SidebarMenu,
|
SidebarMenu,
|
||||||
SidebarMenuButton,
|
SidebarMenuButton,
|
||||||
SidebarMenuItem,
|
SidebarMenuItem,
|
||||||
} from "@/components/ui/sidebar";
|
} from "@/components/ui/sidebar";
|
||||||
import { useI18n } from "@/core/i18n/hooks";
|
import { useI18n } from "@/core/i18n/hooks";
|
||||||
|
|
||||||
|
import { GithubIcon } from "./github-icon";
|
||||||
|
import { SettingsDialog } from "./settings";
|
||||||
|
|
||||||
export function WorkspaceNavMenu() {
|
export function WorkspaceNavMenu() {
|
||||||
|
const [settingsOpen, setSettingsOpen] = useState(false);
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const pathname = usePathname();
|
|
||||||
return (
|
return (
|
||||||
<SidebarGroup className="pt-1">
|
<>
|
||||||
<SidebarMenu>
|
<SettingsDialog open={settingsOpen} onOpenChange={setSettingsOpen} />
|
||||||
|
<SidebarMenu className="w-full">
|
||||||
<SidebarMenuItem>
|
<SidebarMenuItem>
|
||||||
<SidebarMenuButton isActive={pathname === "/workspace/chats"} asChild>
|
<DropdownMenu>
|
||||||
<Link className="text-muted-foreground" href="/workspace/chats">
|
<DropdownMenuTrigger asChild>
|
||||||
<MessagesSquare />
|
<SidebarMenuButton
|
||||||
<span>{t.sidebar.chats}</span>
|
size="lg"
|
||||||
</Link>
|
className="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground"
|
||||||
|
>
|
||||||
|
<div className="flex w-full items-center gap-2">
|
||||||
|
<div className="text-muted-foreground flex items-center gap-2 text-left text-sm leading-tight">
|
||||||
|
<SettingsIcon className="size-4" />
|
||||||
|
{t.workspace.settingsAndMore}
|
||||||
|
</div>
|
||||||
|
<ChevronsUpDown className="text-muted-foreground ml-auto size-4" />
|
||||||
|
</div>
|
||||||
</SidebarMenuButton>
|
</SidebarMenuButton>
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
<DropdownMenuContent
|
||||||
|
className="w-(--radix-dropdown-menu-trigger-width) min-w-56 rounded-lg"
|
||||||
|
align="end"
|
||||||
|
sideOffset={4}
|
||||||
|
>
|
||||||
|
<DropdownMenuGroup>
|
||||||
|
<DropdownMenuItem onClick={() => setSettingsOpen(true)}>
|
||||||
|
<Settings2Icon />
|
||||||
|
{t.common.settings}
|
||||||
|
</DropdownMenuItem>
|
||||||
|
<DropdownMenuSeparator />
|
||||||
|
<a
|
||||||
|
href="https://github.com/bytedance/deer-flow"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
|
<DropdownMenuItem>
|
||||||
|
<GithubIcon />
|
||||||
|
{t.workspace.visitGithub}
|
||||||
|
</DropdownMenuItem>
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
href="https://github.com/bytedance/deer-flow/issues"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
|
<DropdownMenuItem>
|
||||||
|
<BugIcon />
|
||||||
|
{t.workspace.reportIssue}
|
||||||
|
</DropdownMenuItem>
|
||||||
|
</a>
|
||||||
|
<DropdownMenuItem>
|
||||||
|
<MailIcon />
|
||||||
|
{t.workspace.contactUs}
|
||||||
|
</DropdownMenuItem>
|
||||||
|
</DropdownMenuGroup>
|
||||||
|
<DropdownMenuSeparator />
|
||||||
|
<DropdownMenuItem>
|
||||||
|
<InfoIcon />
|
||||||
|
{t.workspace.about}
|
||||||
|
</DropdownMenuItem>
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
</SidebarMenuItem>
|
</SidebarMenuItem>
|
||||||
</SidebarMenu>
|
</SidebarMenu>
|
||||||
</SidebarGroup>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,33 +1,21 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { SettingsIcon } from "lucide-react";
|
|
||||||
import { useState } from "react";
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Sidebar,
|
Sidebar,
|
||||||
SidebarHeader,
|
SidebarHeader,
|
||||||
SidebarContent,
|
SidebarContent,
|
||||||
SidebarFooter,
|
SidebarFooter,
|
||||||
SidebarRail,
|
SidebarRail,
|
||||||
SidebarMenu,
|
|
||||||
SidebarMenuItem,
|
|
||||||
SidebarMenuButton,
|
|
||||||
SidebarGroup,
|
|
||||||
SidebarGroupContent,
|
|
||||||
} from "@/components/ui/sidebar";
|
} from "@/components/ui/sidebar";
|
||||||
import { SettingsDialog } from "@/components/workspace/settings";
|
|
||||||
import { useI18n } from "@/core/i18n/hooks";
|
|
||||||
|
|
||||||
import { RecentChatList } from "./recent-chat-list";
|
import { RecentChatList } from "./recent-chat-list";
|
||||||
import { WorkspaceHeader } from "./workspace-header";
|
import { WorkspaceHeader } from "./workspace-header";
|
||||||
|
import { WorkspaceNavChatList } from "./workspace-nav-chat-list";
|
||||||
import { WorkspaceNavMenu } from "./workspace-nav-menu";
|
import { WorkspaceNavMenu } from "./workspace-nav-menu";
|
||||||
|
|
||||||
export function WorkspaceSidebar({
|
export function WorkspaceSidebar({
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof Sidebar>) {
|
}: React.ComponentProps<typeof Sidebar>) {
|
||||||
const { t } = useI18n();
|
|
||||||
const [settingsOpen, setSettingsOpen] = useState(false);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Sidebar variant="sidebar" collapsible="icon" {...props}>
|
<Sidebar variant="sidebar" collapsible="icon" {...props}>
|
||||||
@@ -35,33 +23,14 @@ export function WorkspaceSidebar({
|
|||||||
<WorkspaceHeader />
|
<WorkspaceHeader />
|
||||||
</SidebarHeader>
|
</SidebarHeader>
|
||||||
<SidebarContent>
|
<SidebarContent>
|
||||||
<WorkspaceNavMenu />
|
<WorkspaceNavChatList />
|
||||||
<RecentChatList />
|
<RecentChatList />
|
||||||
</SidebarContent>
|
</SidebarContent>
|
||||||
<SidebarFooter>
|
<SidebarFooter>
|
||||||
<SidebarGroup className="px-0">
|
<WorkspaceNavMenu />
|
||||||
<SidebarGroupContent>
|
|
||||||
<SidebarMenu>
|
|
||||||
<SidebarMenuItem>
|
|
||||||
<SidebarMenuButton asChild>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className="text-muted-foreground flex w-full cursor-pointer items-center gap-2"
|
|
||||||
onClick={() => setSettingsOpen(true)}
|
|
||||||
>
|
|
||||||
<SettingsIcon size={16} />
|
|
||||||
<span>{t.common.settings}</span>
|
|
||||||
</button>
|
|
||||||
</SidebarMenuButton>
|
|
||||||
</SidebarMenuItem>
|
|
||||||
</SidebarMenu>
|
|
||||||
</SidebarGroupContent>
|
|
||||||
</SidebarGroup>
|
|
||||||
</SidebarFooter>
|
</SidebarFooter>
|
||||||
<SidebarRail />
|
<SidebarRail />
|
||||||
</Sidebar>
|
</Sidebar>
|
||||||
|
|
||||||
<SettingsDialog open={settingsOpen} onOpenChange={setSettingsOpen} />
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -68,6 +68,11 @@ export const enUS: Translations = {
|
|||||||
// Workspace
|
// Workspace
|
||||||
workspace: {
|
workspace: {
|
||||||
githubTooltip: "DeerFlow on Github",
|
githubTooltip: "DeerFlow on Github",
|
||||||
|
settingsAndMore: "Settings and more",
|
||||||
|
visitGithub: "Visit DeerFlow on GitHub",
|
||||||
|
reportIssue: "Report a issue",
|
||||||
|
contactUs: "Contact us",
|
||||||
|
about: "About",
|
||||||
},
|
},
|
||||||
|
|
||||||
// Conversation
|
// Conversation
|
||||||
|
|||||||
@@ -63,6 +63,11 @@ export interface Translations {
|
|||||||
// Workspace
|
// Workspace
|
||||||
workspace: {
|
workspace: {
|
||||||
githubTooltip: string;
|
githubTooltip: string;
|
||||||
|
settingsAndMore: string;
|
||||||
|
visitGithub: string;
|
||||||
|
reportIssue: string;
|
||||||
|
contactUs: string;
|
||||||
|
about: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Conversation
|
// Conversation
|
||||||
|
|||||||
@@ -66,6 +66,11 @@ export const zhCN: Translations = {
|
|||||||
// Workspace
|
// Workspace
|
||||||
workspace: {
|
workspace: {
|
||||||
githubTooltip: "DeerFlow 在 Github",
|
githubTooltip: "DeerFlow 在 Github",
|
||||||
|
settingsAndMore: "设置和更多",
|
||||||
|
visitGithub: "在 Github 上查看 DeerFlow",
|
||||||
|
reportIssue: "报告问题",
|
||||||
|
contactUs: "联系我们",
|
||||||
|
about: "关于",
|
||||||
},
|
},
|
||||||
|
|
||||||
// Conversation
|
// Conversation
|
||||||
|
|||||||
Reference in New Issue
Block a user