mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-08-21 17:57:14 +00:00
feat: add plugin extension pages (iframe rendering, Page SDK, security hardening, i18n)
Co-Authored-By: Typer_Body <mcjiekejiemi@163.com>
This commit is contained in:
@@ -92,6 +92,7 @@ import {
|
||||
} from '@/components/ui/popover';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { useSidebarData, SidebarEntityItem } from './SidebarDataContext';
|
||||
import { LayoutDashboard } from 'lucide-react';
|
||||
|
||||
// Compare two version strings, returns true if v1 > v2
|
||||
function compareVersions(v1: string, v2: string): boolean {
|
||||
@@ -699,87 +700,103 @@ function NavItems({
|
||||
>
|
||||
<SidebarMenuItem>
|
||||
<SidebarMenuButton
|
||||
asChild
|
||||
isActive={false}
|
||||
onClick={() => {
|
||||
if (isCollapseOnly) {
|
||||
onSectionToggle(config.id, !isOpen);
|
||||
} else {
|
||||
onChildClick(config);
|
||||
}
|
||||
}}
|
||||
tooltip={config.name}
|
||||
className="group/category-header"
|
||||
>
|
||||
{config.icon}
|
||||
<span>{config.name}</span>
|
||||
<div className="ml-auto flex items-center gap-0.5 -mr-1">
|
||||
{canCreate &&
|
||||
(isPlugin ? (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<button
|
||||
type="button"
|
||||
className="p-1 rounded-sm text-sidebar-foreground/70 hover:bg-sidebar-accent hover:text-sidebar-accent-foreground [@media(hover:hover)]:opacity-0 group-hover/category-header:opacity-100 transition-all"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<Plus className="size-3.5" />
|
||||
</button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
{systemInfo.enable_marketplace && (
|
||||
<div
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
onClick={() => {
|
||||
if (isCollapseOnly) {
|
||||
onSectionToggle(config.id, !isOpen);
|
||||
} else {
|
||||
onChildClick(config);
|
||||
}
|
||||
}}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter' || e.key === ' ') {
|
||||
e.preventDefault();
|
||||
if (isCollapseOnly) {
|
||||
onSectionToggle(config.id, !isOpen);
|
||||
} else {
|
||||
onChildClick(config);
|
||||
}
|
||||
}
|
||||
}}
|
||||
>
|
||||
{config.icon}
|
||||
<span>{config.name}</span>
|
||||
<div className="ml-auto flex items-center gap-0.5 -mr-1">
|
||||
{canCreate &&
|
||||
(isPlugin ? (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<button
|
||||
type="button"
|
||||
className="p-1 rounded-sm text-sidebar-foreground/70 hover:bg-sidebar-accent hover:text-sidebar-accent-foreground [@media(hover:hover)]:opacity-0 group-hover/category-header:opacity-100 transition-all"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<Plus className="size-3.5" />
|
||||
</button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
{systemInfo.enable_marketplace && (
|
||||
<DropdownMenuItem
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
navigate('/home/market');
|
||||
}}
|
||||
>
|
||||
<Store className="size-4" />
|
||||
{t('plugins.goToMarketplace')}
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
<DropdownMenuItem
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
navigate('/home/market');
|
||||
setPendingPluginInstallAction('local');
|
||||
navigate('/home/plugins');
|
||||
}}
|
||||
>
|
||||
<Store className="size-4" />
|
||||
{t('plugins.goToMarketplace')}
|
||||
<Upload className="size-4" />
|
||||
{t('plugins.uploadLocal')}
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
<DropdownMenuItem
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
setPendingPluginInstallAction('local');
|
||||
navigate('/home/plugins');
|
||||
}}
|
||||
>
|
||||
<Upload className="size-4" />
|
||||
{t('plugins.uploadLocal')}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
setPendingPluginInstallAction('github');
|
||||
navigate('/home/plugins');
|
||||
}}
|
||||
>
|
||||
<Github className="size-4" />
|
||||
{t('plugins.installFromGithub')}
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
) : (
|
||||
<DropdownMenuItem
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
setPendingPluginInstallAction('github');
|
||||
navigate('/home/plugins');
|
||||
}}
|
||||
>
|
||||
<Github className="size-4" />
|
||||
{t('plugins.installFromGithub')}
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
) : (
|
||||
<button
|
||||
type="button"
|
||||
className="p-1 rounded-sm text-sidebar-foreground/70 hover:bg-sidebar-accent hover:text-sidebar-accent-foreground [@media(hover:hover)]:opacity-0 group-hover/category-header:opacity-100 transition-all"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
navigate(`${routePrefix}?id=new`);
|
||||
}}
|
||||
>
|
||||
<Plus className="size-3.5" />
|
||||
</button>
|
||||
))}
|
||||
<CollapsibleTrigger asChild>
|
||||
<button
|
||||
type="button"
|
||||
className="p-1 rounded-sm text-sidebar-foreground/70 hover:bg-sidebar-accent hover:text-sidebar-accent-foreground [@media(hover:hover)]:opacity-0 group-hover/category-header:opacity-100 transition-all"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
navigate(`${routePrefix}?id=new`);
|
||||
}}
|
||||
className="p-1 rounded-sm hover:bg-sidebar-accent"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<Plus className="size-3.5" />
|
||||
<ChevronRight className="size-4 transition-transform duration-200 group-data-[state=open]/collapsible:rotate-90" />
|
||||
</button>
|
||||
))}
|
||||
<CollapsibleTrigger asChild>
|
||||
<button
|
||||
type="button"
|
||||
className="p-1 rounded-sm hover:bg-sidebar-accent"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<ChevronRight className="size-4 transition-transform duration-200 group-data-[state=open]/collapsible:rotate-90" />
|
||||
</button>
|
||||
</CollapsibleTrigger>
|
||||
</CollapsibleTrigger>
|
||||
</div>
|
||||
</div>
|
||||
</SidebarMenuButton>
|
||||
<CollapsibleContent>
|
||||
@@ -1023,6 +1040,47 @@ function PluginItemMenu({
|
||||
);
|
||||
}
|
||||
|
||||
// Plugin pages navigation section
|
||||
function PluginPagesNav() {
|
||||
const { pluginPages } = useSidebarData();
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
const [searchParams] = useSearchParams();
|
||||
const { t } = useTranslation();
|
||||
|
||||
if (pluginPages.length === 0) return null;
|
||||
|
||||
const pathname = location.pathname;
|
||||
const currentId =
|
||||
pathname === '/home/plugin-pages' ? searchParams.get('id') : null;
|
||||
|
||||
return (
|
||||
<SidebarGroup>
|
||||
<SidebarGroupLabel>{t('sidebar.pluginPages')}</SidebarGroupLabel>
|
||||
<SidebarGroupContent>
|
||||
<SidebarMenu>
|
||||
{pluginPages.map((page) => {
|
||||
const isActive = currentId === page.id;
|
||||
const route = `/home/plugin-pages?id=${encodeURIComponent(page.id)}`;
|
||||
return (
|
||||
<SidebarMenuItem key={page.id}>
|
||||
<SidebarMenuButton
|
||||
isActive={isActive}
|
||||
tooltip={page.name}
|
||||
onClick={() => navigate(route)}
|
||||
>
|
||||
<LayoutDashboard className="size-4 text-blue-500" />
|
||||
<span>{page.name}</span>
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem>
|
||||
);
|
||||
})}
|
||||
</SidebarMenu>
|
||||
</SidebarGroupContent>
|
||||
</SidebarGroup>
|
||||
);
|
||||
}
|
||||
|
||||
export default function HomeSidebar({
|
||||
onSelectedChangeAction,
|
||||
}: {
|
||||
@@ -1285,6 +1343,7 @@ export default function HomeSidebar({
|
||||
</SidebarMenu>
|
||||
</SidebarGroupContent>
|
||||
</SidebarGroup>
|
||||
<PluginPagesNav />
|
||||
</SidebarContent>
|
||||
|
||||
{/* Footer */}
|
||||
|
||||
Reference in New Issue
Block a user