feat: 添加MinIO文件系统支持并优化外部连接器功能
- 添加MinIO用户文件系统配置选项(BEAVER_MINIO_ROOT_USER等) - 更新外部连接器配置结构,包括BASE_URL和认证令牌设置 - 改进connector provider支持更多类型(official, feishu_bot等) - 实现Mistral模型推理模式支持reasoning_effort参数 - 增强外部连接器策略配置和运行时配置管理 - 添加connector bridge事件验证和安全保护机制 - 优化任务路由逻辑,区分simple_chat和new_task场景 - 更新初始技能工具提示配置,分离authoring admin功能
This commit is contained in:
@ -3,7 +3,7 @@
|
||||
import React from 'react';
|
||||
import Link from 'next/link';
|
||||
import { usePathname, useRouter } from 'next/navigation';
|
||||
import { Bell, Bot, ChevronDown, FolderOpen, ListTodo, LogOut, Mail, MessageSquare, Puzzle, Settings, Store, Wrench } from 'lucide-react';
|
||||
import { Bell, Bot, ChevronDown, FolderOpen, ListTodo, LogOut, Mail, Menu, MessageSquare, Puzzle, Settings, Store, Wrench, X } from 'lucide-react';
|
||||
import { logout } from '@/lib/api';
|
||||
import { LanguageSwitcher } from '@/components/LanguageSwitcher';
|
||||
import { Avatar, AvatarFallback } from '@/components/ui/avatar';
|
||||
@ -69,6 +69,7 @@ const Header = () => {
|
||||
const { locale } = useAppI18n();
|
||||
const pathname = usePathname();
|
||||
const router = useRouter();
|
||||
const [mobileMenuOpen, setMobileMenuOpen] = React.useState(false);
|
||||
const user = useChatStore((s) => s.user);
|
||||
const isAuthLoading = useChatStore((s) => s.isAuthLoading);
|
||||
const setUser = useChatStore((s) => s.setUser);
|
||||
@ -93,108 +94,175 @@ const Header = () => {
|
||||
router.refresh();
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
setMobileMenuOpen(false);
|
||||
}, [pathname]);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!mobileMenuOpen) return;
|
||||
|
||||
const previousOverflow = document.body.style.overflow;
|
||||
const handleKeyDown = (event: KeyboardEvent) => {
|
||||
if (event.key === 'Escape') {
|
||||
setMobileMenuOpen(false);
|
||||
}
|
||||
};
|
||||
|
||||
document.body.style.overflow = 'hidden';
|
||||
document.addEventListener('keydown', handleKeyDown);
|
||||
|
||||
return () => {
|
||||
document.body.style.overflow = previousOverflow;
|
||||
document.removeEventListener('keydown', handleKeyDown);
|
||||
};
|
||||
}, [mobileMenuOpen]);
|
||||
|
||||
const userInitial = (user?.username || user?.email || '?').trim().charAt(0).toUpperCase();
|
||||
|
||||
return (
|
||||
<header className="fixed left-0 right-0 top-0 z-50 border-b border-[#E6E1DE] bg-[#F7F6F5]/95 backdrop-blur">
|
||||
<div className="mx-auto max-w-[1720px] px-4 sm:px-6 lg:px-8">
|
||||
<div className="grid h-16 grid-cols-[minmax(120px,1fr)_auto_minmax(120px,1fr)] items-center gap-4">
|
||||
<Link href="/" className="flex shrink-0 items-center">
|
||||
<span className="font-serif text-[28px] font-semibold leading-none text-[#0B0B0B]">
|
||||
Beaver
|
||||
</span>
|
||||
</Link>
|
||||
const renderNavLinks = (compact = false) =>
|
||||
NAV_ITEMS.map((item) => {
|
||||
const isActive =
|
||||
item.href === '/'
|
||||
? pathname === '/'
|
||||
: item.matchPrefixes?.some((prefix) => pathname.startsWith(prefix)) ?? pathname.startsWith(item.href);
|
||||
const Icon = item.icon;
|
||||
return (
|
||||
<Link
|
||||
key={item.href}
|
||||
href={item.href}
|
||||
onClick={compact ? () => setMobileMenuOpen(false) : undefined}
|
||||
className={`flex h-11 shrink-0 items-center gap-2 rounded-full text-sm font-medium transition-colors ${
|
||||
compact ? 'justify-start rounded-lg border border-transparent bg-background px-4' : 'px-4'
|
||||
} ${
|
||||
isActive
|
||||
? 'bg-primary text-primary-foreground'
|
||||
: compact
|
||||
? 'text-[#4F4642] hover:border-[#E6E1DE] hover:bg-muted hover:text-[#0B0B0B]'
|
||||
: 'text-[#4F4642] hover:bg-[#F7F5F4] hover:text-[#0B0B0B]'
|
||||
}`}
|
||||
>
|
||||
<Icon className="h-4 w-4" />
|
||||
{navLabel(item.key)}
|
||||
</Link>
|
||||
);
|
||||
});
|
||||
|
||||
<nav className="flex items-center gap-1 rounded-full border border-[#E6E1DE] bg-white px-1.5 py-1 shadow-[0_1px_2px_rgba(0,0,0,0.04)]">
|
||||
{NAV_ITEMS.map((item) => {
|
||||
const isActive =
|
||||
item.href === '/'
|
||||
? pathname === '/'
|
||||
: item.matchPrefixes?.some((prefix) => pathname.startsWith(prefix)) ?? pathname.startsWith(item.href);
|
||||
const Icon = item.icon;
|
||||
return (
|
||||
<Link
|
||||
key={item.href}
|
||||
href={item.href}
|
||||
className={`flex shrink-0 items-center gap-1.5 rounded-full px-4 py-2 text-sm font-medium transition-colors ${
|
||||
isActive
|
||||
? 'bg-primary text-primary-foreground'
|
||||
: 'text-[#4F4642] hover:bg-[#F7F5F4] hover:text-[#0B0B0B]'
|
||||
}`}
|
||||
>
|
||||
<Icon className="w-4 h-4" />
|
||||
{navLabel(item.key)}
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
return (
|
||||
<>
|
||||
<header className="fixed left-0 right-0 top-0 z-50 border-b border-[#E6E1DE] bg-[#F7F6F5]/95 backdrop-blur">
|
||||
<div className="mx-auto max-w-[1720px] px-4 sm:px-6 lg:px-8">
|
||||
<div className="flex h-16 items-center justify-between gap-3">
|
||||
<div className="flex min-w-0 items-center gap-2">
|
||||
<button
|
||||
type="button"
|
||||
className="inline-flex h-11 w-11 items-center justify-center rounded-full border border-[#E6E1DE] bg-white text-[#1D1715] transition-colors hover:bg-[#F7F5F4] 2xl:hidden"
|
||||
aria-label={mobileMenuOpen ? pickAppText(locale, '关闭导航', 'Close navigation') : pickAppText(locale, '打开导航', 'Open navigation')}
|
||||
aria-expanded={mobileMenuOpen}
|
||||
aria-controls="app-primary-mobile-nav"
|
||||
onClick={() => setMobileMenuOpen((open) => !open)}
|
||||
>
|
||||
{mobileMenuOpen ? <X className="h-5 w-5" /> : <Menu className="h-5 w-5" />}
|
||||
</button>
|
||||
<Link href="/" className="hidden h-11 shrink-0 items-center min-[360px]:flex">
|
||||
<span className="font-serif text-[26px] font-semibold leading-none text-[#0B0B0B] sm:text-[28px]">
|
||||
Beaver
|
||||
</span>
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<nav className="hidden items-center gap-1 rounded-full border border-[#E6E1DE] bg-white px-1.5 py-1 shadow-[0_1px_2px_rgba(0,0,0,0.04)] 2xl:flex">
|
||||
{renderNavLinks(false)}
|
||||
</nav>
|
||||
|
||||
<div className="flex min-w-0 items-center justify-end gap-3">
|
||||
<div className="hidden shrink-0 sm:block">
|
||||
<ConnectionDot />
|
||||
</div>
|
||||
<div className="flex shrink-0 items-center gap-2">
|
||||
<LanguageSwitcher />
|
||||
{user ? (
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<button
|
||||
type="button"
|
||||
className="flex items-center gap-2 rounded-full border border-[#E6E1DE] bg-white px-2 py-1.5 text-sm font-medium text-[#1D1715] transition-colors hover:bg-[#F7F5F4]"
|
||||
>
|
||||
<Avatar className="h-8 w-8 border border-[#E6E1DE]">
|
||||
<AvatarFallback className="bg-primary text-xs font-semibold text-primary-foreground">
|
||||
{userInitial}
|
||||
</AvatarFallback>
|
||||
</Avatar>
|
||||
<span className="hidden max-w-28 truncate sm:block">{user.username}</span>
|
||||
<ChevronDown className="h-4 w-4 text-muted-foreground" />
|
||||
</button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent align="end" className="w-80 rounded-3xl border-border/70 p-0 shadow-2xl">
|
||||
<div className="overflow-hidden rounded-3xl bg-[linear-gradient(180deg,#F7F5F4,#FFFFFF)]">
|
||||
<div className="border-b border-border/60 px-6 py-5">
|
||||
<p className="truncate text-center text-sm font-medium text-muted-foreground">
|
||||
{user.email}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col items-center gap-4 px-6 py-6 text-center">
|
||||
<Avatar className="h-24 w-24 border-4 border-white shadow-sm">
|
||||
<AvatarFallback className="bg-primary text-4xl font-semibold text-primary-foreground">
|
||||
<div className="flex min-w-0 shrink-0 items-center justify-end gap-2 sm:gap-3">
|
||||
<div className="hidden shrink-0 xl:block">
|
||||
<ConnectionDot />
|
||||
</div>
|
||||
<div className="flex shrink-0 items-center gap-2">
|
||||
<LanguageSwitcher />
|
||||
{user ? (
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<button
|
||||
type="button"
|
||||
className="flex h-11 w-11 items-center justify-center gap-2 rounded-full border border-[#E6E1DE] bg-white px-1.5 text-sm font-medium text-[#1D1715] transition-colors hover:bg-[#F7F5F4] sm:w-auto sm:justify-start sm:px-2"
|
||||
aria-label={pickAppText(locale, '打开账号菜单', 'Open account menu')}
|
||||
>
|
||||
<Avatar className="h-8 w-8 border border-[#E6E1DE]">
|
||||
<AvatarFallback className="bg-primary text-xs font-semibold text-primary-foreground">
|
||||
{userInitial}
|
||||
</AvatarFallback>
|
||||
</Avatar>
|
||||
<div className="space-y-1">
|
||||
<p className="text-2xl font-semibold tracking-tight text-foreground">
|
||||
{pickAppText(locale, `${user.username},你好!`, `Hi, ${user.username}`)}
|
||||
</p>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{pickAppText(locale, '当前已登录到你的工作区实例。', 'You are currently signed in to your workspace instance.')}
|
||||
<span className="hidden max-w-28 truncate sm:block">{user.username}</span>
|
||||
<ChevronDown className="hidden h-4 w-4 text-muted-foreground sm:block" />
|
||||
</button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent align="end" className="w-80 rounded-3xl border-border/70 p-0 shadow-2xl">
|
||||
<div className="overflow-hidden rounded-3xl bg-[linear-gradient(180deg,#F7F5F4,#FFFFFF)]">
|
||||
<div className="border-b border-border/60 px-6 py-5">
|
||||
<p className="truncate text-center text-sm font-medium text-muted-foreground">
|
||||
{user.email}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="border-t border-border/60 bg-white/90 px-4 py-4">
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
onClick={handleLogout}
|
||||
className="h-12 w-full justify-center rounded-2xl text-sm font-semibold"
|
||||
>
|
||||
<LogOut className="mr-2 h-4 w-4" />
|
||||
{pickAppText(locale, '退出登录', 'Sign Out')}
|
||||
</Button>
|
||||
<div className="flex flex-col items-center gap-4 px-6 py-6 text-center">
|
||||
<Avatar className="h-24 w-24 border-4 border-white shadow-sm">
|
||||
<AvatarFallback className="bg-primary text-4xl font-semibold text-primary-foreground">
|
||||
{userInitial}
|
||||
</AvatarFallback>
|
||||
</Avatar>
|
||||
<div className="space-y-1">
|
||||
<p className="text-2xl font-semibold tracking-tight text-foreground">
|
||||
{pickAppText(locale, `${user.username},你好!`, `Hi, ${user.username}`)}
|
||||
</p>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{pickAppText(locale, '当前已登录到你的工作区实例。', 'You are currently signed in to your workspace instance.')}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="border-t border-border/60 bg-white/90 px-4 py-4">
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
onClick={handleLogout}
|
||||
className="h-12 w-full justify-center rounded-2xl text-sm font-semibold"
|
||||
>
|
||||
<LogOut className="mr-2 h-4 w-4" />
|
||||
{pickAppText(locale, '退出登录', 'Sign Out')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
) : !isAuthLoading ? null : null}
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
) : !isAuthLoading ? null : null}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
</header>
|
||||
{mobileMenuOpen && (
|
||||
<>
|
||||
<button
|
||||
type="button"
|
||||
className="fixed inset-x-0 bottom-0 top-16 z-40 bg-black/40 2xl:hidden"
|
||||
aria-label={pickAppText(locale, '关闭导航', 'Close navigation')}
|
||||
onClick={() => setMobileMenuOpen(false)}
|
||||
/>
|
||||
<nav
|
||||
id="app-primary-mobile-nav"
|
||||
aria-label={pickAppText(locale, '主导航', 'Primary navigation')}
|
||||
className="fixed bottom-0 left-0 top-16 z-[45] isolate w-[min(86vw,320px)] overflow-y-auto border-r border-[#E6E1DE] bg-background text-foreground shadow-[12px_0_32px_rgba(29,23,21,0.24)] animate-in slide-in-from-left-full duration-200 2xl:hidden"
|
||||
>
|
||||
<div className="min-h-full bg-background px-4 py-5">
|
||||
<div className="grid gap-2 bg-background">
|
||||
{renderNavLinks(true)}
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user