mirror of
https://gitee.com/wanwujie/deer-flow
synced 2026-04-03 06:12:14 +08:00
feat: add main menu
This commit is contained in:
@@ -25,6 +25,7 @@
|
||||
"@codemirror/language-data": "^6.5.2",
|
||||
"@langchain/core": "^1.1.15",
|
||||
"@langchain/langgraph-sdk": "^1.5.3",
|
||||
"@radix-ui/react-avatar": "^1.1.11",
|
||||
"@radix-ui/react-collapsible": "^1.1.12",
|
||||
"@radix-ui/react-dialog": "^1.1.15",
|
||||
"@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':
|
||||
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)
|
||||
'@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':
|
||||
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)
|
||||
@@ -1011,6 +1014,19 @@ packages:
|
||||
'@types/react-dom':
|
||||
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':
|
||||
resolution: {integrity: sha512-Uu+mSh4agx2ib1uIGPP4/CKNULyajb3p92LsVXmH2EHVMTfZWpll88XJ0j4W0z3f8NK1eYl1+Mf/szHPmcHzyA==}
|
||||
peerDependencies:
|
||||
@@ -1405,6 +1421,15 @@ packages:
|
||||
'@types/react':
|
||||
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':
|
||||
resolution: {integrity: sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==}
|
||||
peerDependencies:
|
||||
@@ -5963,6 +5988,19 @@ snapshots:
|
||||
'@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)':
|
||||
dependencies:
|
||||
'@radix-ui/primitive': 1.1.3
|
||||
@@ -6373,6 +6411,13 @@ snapshots:
|
||||
optionalDependencies:
|
||||
'@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)':
|
||||
dependencies:
|
||||
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";
|
||||
|
||||
import { MessagesSquare } from "lucide-react";
|
||||
import Link from "next/link";
|
||||
import { usePathname } from "next/navigation";
|
||||
import {
|
||||
BugIcon,
|
||||
ChevronsUpDown,
|
||||
InfoIcon,
|
||||
MailIcon,
|
||||
Settings2Icon,
|
||||
SettingsIcon,
|
||||
} from "lucide-react";
|
||||
import { useState } from "react";
|
||||
|
||||
import {
|
||||
SidebarGroup,
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuGroup,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import {
|
||||
SidebarMenu,
|
||||
SidebarMenuButton,
|
||||
SidebarMenuItem,
|
||||
} from "@/components/ui/sidebar";
|
||||
import { useI18n } from "@/core/i18n/hooks";
|
||||
|
||||
import { GithubIcon } from "./github-icon";
|
||||
import { SettingsDialog } from "./settings";
|
||||
|
||||
export function WorkspaceNavMenu() {
|
||||
const [settingsOpen, setSettingsOpen] = useState(false);
|
||||
const { t } = useI18n();
|
||||
const pathname = usePathname();
|
||||
return (
|
||||
<SidebarGroup className="pt-1">
|
||||
<SidebarMenu>
|
||||
<>
|
||||
<SettingsDialog open={settingsOpen} onOpenChange={setSettingsOpen} />
|
||||
<SidebarMenu className="w-full">
|
||||
<SidebarMenuItem>
|
||||
<SidebarMenuButton isActive={pathname === "/workspace/chats"} asChild>
|
||||
<Link className="text-muted-foreground" href="/workspace/chats">
|
||||
<MessagesSquare />
|
||||
<span>{t.sidebar.chats}</span>
|
||||
</Link>
|
||||
</SidebarMenuButton>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<SidebarMenuButton
|
||||
size="lg"
|
||||
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>
|
||||
</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>
|
||||
</SidebarMenu>
|
||||
</SidebarGroup>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,33 +1,21 @@
|
||||
"use client";
|
||||
|
||||
import { SettingsIcon } from "lucide-react";
|
||||
import { useState } from "react";
|
||||
|
||||
import {
|
||||
Sidebar,
|
||||
SidebarHeader,
|
||||
SidebarContent,
|
||||
SidebarFooter,
|
||||
SidebarRail,
|
||||
SidebarMenu,
|
||||
SidebarMenuItem,
|
||||
SidebarMenuButton,
|
||||
SidebarGroup,
|
||||
SidebarGroupContent,
|
||||
} from "@/components/ui/sidebar";
|
||||
import { SettingsDialog } from "@/components/workspace/settings";
|
||||
import { useI18n } from "@/core/i18n/hooks";
|
||||
|
||||
import { RecentChatList } from "./recent-chat-list";
|
||||
import { WorkspaceHeader } from "./workspace-header";
|
||||
import { WorkspaceNavChatList } from "./workspace-nav-chat-list";
|
||||
import { WorkspaceNavMenu } from "./workspace-nav-menu";
|
||||
|
||||
export function WorkspaceSidebar({
|
||||
...props
|
||||
}: React.ComponentProps<typeof Sidebar>) {
|
||||
const { t } = useI18n();
|
||||
const [settingsOpen, setSettingsOpen] = useState(false);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Sidebar variant="sidebar" collapsible="icon" {...props}>
|
||||
@@ -35,33 +23,14 @@ export function WorkspaceSidebar({
|
||||
<WorkspaceHeader />
|
||||
</SidebarHeader>
|
||||
<SidebarContent>
|
||||
<WorkspaceNavMenu />
|
||||
<WorkspaceNavChatList />
|
||||
<RecentChatList />
|
||||
</SidebarContent>
|
||||
<SidebarFooter>
|
||||
<SidebarGroup className="px-0">
|
||||
<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>
|
||||
<WorkspaceNavMenu />
|
||||
</SidebarFooter>
|
||||
<SidebarRail />
|
||||
</Sidebar>
|
||||
|
||||
<SettingsDialog open={settingsOpen} onOpenChange={setSettingsOpen} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -68,6 +68,11 @@ export const enUS: Translations = {
|
||||
// Workspace
|
||||
workspace: {
|
||||
githubTooltip: "DeerFlow on Github",
|
||||
settingsAndMore: "Settings and more",
|
||||
visitGithub: "Visit DeerFlow on GitHub",
|
||||
reportIssue: "Report a issue",
|
||||
contactUs: "Contact us",
|
||||
about: "About",
|
||||
},
|
||||
|
||||
// Conversation
|
||||
|
||||
@@ -63,6 +63,11 @@ export interface Translations {
|
||||
// Workspace
|
||||
workspace: {
|
||||
githubTooltip: string;
|
||||
settingsAndMore: string;
|
||||
visitGithub: string;
|
||||
reportIssue: string;
|
||||
contactUs: string;
|
||||
about: string;
|
||||
};
|
||||
|
||||
// Conversation
|
||||
|
||||
@@ -66,6 +66,11 @@ export const zhCN: Translations = {
|
||||
// Workspace
|
||||
workspace: {
|
||||
githubTooltip: "DeerFlow 在 Github",
|
||||
settingsAndMore: "设置和更多",
|
||||
visitGithub: "在 Github 上查看 DeerFlow",
|
||||
reportIssue: "报告问题",
|
||||
contactUs: "联系我们",
|
||||
about: "关于",
|
||||
},
|
||||
|
||||
// Conversation
|
||||
|
||||
Reference in New Issue
Block a user