mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-08-11 05:20:57 +00:00
feat(sidebar): add group-by-kind toggle to Agent section
Add a toggle (left of the "+" button) that groups the Agent section by kind, showing "Agent" and "Pipeline" sub-headers. State persists in localStorage, mirroring the extensions group-by-type pattern. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -34,6 +34,7 @@ import {
|
|||||||
RefreshCcw,
|
RefreshCcw,
|
||||||
Bot,
|
Bot,
|
||||||
Workflow,
|
Workflow,
|
||||||
|
Group,
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import { useTheme } from '@/components/providers/theme-provider';
|
import { useTheme } from '@/components/providers/theme-provider';
|
||||||
|
|
||||||
@@ -509,6 +510,7 @@ function NavItems({
|
|||||||
const isSkill = categoryId === 'skills';
|
const isSkill = categoryId === 'skills';
|
||||||
const isBot = categoryId === 'bots';
|
const isBot = categoryId === 'bots';
|
||||||
const isMCP = categoryId === 'mcp';
|
const isMCP = categoryId === 'mcp';
|
||||||
|
const isAgents = categoryId === 'pipelines';
|
||||||
|
|
||||||
const resolveItemRoute = (item: SidebarEntityItem): string => {
|
const resolveItemRoute = (item: SidebarEntityItem): string => {
|
||||||
if (item.extensionType === 'mcp') {
|
if (item.extensionType === 'mcp') {
|
||||||
@@ -591,6 +593,18 @@ function NavItems({
|
|||||||
!inPopover &&
|
!inPopover &&
|
||||||
sidebarData.extensionsGroupByType;
|
sidebarData.extensionsGroupByType;
|
||||||
|
|
||||||
|
const showAgentGroupHeaders =
|
||||||
|
isAgents && !inPopover && sidebarData.agentsGroupByKind;
|
||||||
|
|
||||||
|
const agentGroupOrder: Array<'agent' | 'pipeline'> = [
|
||||||
|
'agent',
|
||||||
|
'pipeline',
|
||||||
|
];
|
||||||
|
const agentGroupLabelKey: Record<'agent' | 'pipeline', string> = {
|
||||||
|
agent: 'agents.kindBadgeAgent',
|
||||||
|
pipeline: 'agents.kindBadgePipeline',
|
||||||
|
};
|
||||||
|
|
||||||
const groupOrder: Array<'plugin' | 'mcp' | 'skill'> = [
|
const groupOrder: Array<'plugin' | 'mcp' | 'skill'> = [
|
||||||
'plugin',
|
'plugin',
|
||||||
'mcp',
|
'mcp',
|
||||||
@@ -792,7 +806,25 @@ function NavItems({
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
: visibleItems.map((item) => renderItem(item))}
|
: showAgentGroupHeaders
|
||||||
|
? agentGroupOrder.map((kind) => {
|
||||||
|
const groupItems = visibleItems.filter(
|
||||||
|
(it) => (it.kind ?? 'agent') === kind,
|
||||||
|
);
|
||||||
|
if (groupItems.length === 0) return null;
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={kind}
|
||||||
|
className="flex flex-col gap-0.5 mt-0.5"
|
||||||
|
>
|
||||||
|
<div className="px-2 pt-1 pb-0.5 text-[0.65rem] font-semibold uppercase tracking-wide text-muted-foreground">
|
||||||
|
{t(agentGroupLabelKey[kind])}
|
||||||
|
</div>
|
||||||
|
{groupItems.map((item) => renderItem(item))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})
|
||||||
|
: visibleItems.map((item) => renderItem(item))}
|
||||||
{/* Show more / less toggle when items exceed limit */}
|
{/* Show more / less toggle when items exceed limit */}
|
||||||
{sortedItems.length > maxItems && !inPopover && (
|
{sortedItems.length > maxItems && !inPopover && (
|
||||||
<SidebarMenuSubItem>
|
<SidebarMenuSubItem>
|
||||||
@@ -1030,6 +1062,26 @@ function NavItems({
|
|||||||
{config.name}
|
{config.name}
|
||||||
</span>
|
</span>
|
||||||
<div className="ml-auto flex items-center gap-0.5 -mr-1">
|
<div className="ml-auto flex items-center gap-0.5 -mr-1">
|
||||||
|
{isAgents && (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
title={t('agents.groupByKind')}
|
||||||
|
className={cn(
|
||||||
|
'p-1 rounded-sm transition-all',
|
||||||
|
sidebarData.agentsGroupByKind
|
||||||
|
? 'text-sidebar-accent-foreground bg-sidebar-accent'
|
||||||
|
: 'text-sidebar-foreground/70 hover:bg-sidebar-accent hover:text-sidebar-accent-foreground [@media(hover:hover)]:opacity-0 group-hover/category-header:opacity-100',
|
||||||
|
)}
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
sidebarData.setAgentsGroupByKind(
|
||||||
|
!sidebarData.agentsGroupByKind,
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Group className="size-3.5" />
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
{isExtensionsCategory && (
|
{isExtensionsCategory && (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
|||||||
@@ -66,6 +66,9 @@ export interface SidebarDataContextValue {
|
|||||||
// Whether the extensions list is grouped by type (shared between page and sidebar)
|
// Whether the extensions list is grouped by type (shared between page and sidebar)
|
||||||
extensionsGroupByType: boolean;
|
extensionsGroupByType: boolean;
|
||||||
setExtensionsGroupByType: (enabled: boolean) => void;
|
setExtensionsGroupByType: (enabled: boolean) => void;
|
||||||
|
// Whether the Agent list is grouped by kind (Agent vs Pipeline)
|
||||||
|
agentsGroupByKind: boolean;
|
||||||
|
setAgentsGroupByKind: (enabled: boolean) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const SidebarDataContext = createContext<SidebarDataContextValue | null>(null);
|
const SidebarDataContext = createContext<SidebarDataContextValue | null>(null);
|
||||||
@@ -97,6 +100,21 @@ export function SidebarDataProvider({
|
|||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const [agentsGroupByKind, setAgentsGroupByKindState] = useState<boolean>(
|
||||||
|
() => {
|
||||||
|
if (typeof window === 'undefined') return false;
|
||||||
|
return localStorage.getItem('agents_group_by_kind') === 'true';
|
||||||
|
},
|
||||||
|
);
|
||||||
|
const setAgentsGroupByKind = useCallback((enabled: boolean) => {
|
||||||
|
setAgentsGroupByKindState(enabled);
|
||||||
|
try {
|
||||||
|
localStorage.setItem('agents_group_by_kind', String(enabled));
|
||||||
|
} catch {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
const refreshBots = useCallback(async () => {
|
const refreshBots = useCallback(async () => {
|
||||||
try {
|
try {
|
||||||
const resp = await httpClient.getBots();
|
const resp = await httpClient.getBots();
|
||||||
@@ -311,6 +329,8 @@ export function SidebarDataProvider({
|
|||||||
setDetailEntityName,
|
setDetailEntityName,
|
||||||
extensionsGroupByType,
|
extensionsGroupByType,
|
||||||
setExtensionsGroupByType,
|
setExtensionsGroupByType,
|
||||||
|
agentsGroupByKind,
|
||||||
|
setAgentsGroupByKind,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
|
|||||||
@@ -519,6 +519,7 @@ const enUS = {
|
|||||||
pipelineType: 'Pipeline',
|
pipelineType: 'Pipeline',
|
||||||
kindBadgeAgent: 'Agent',
|
kindBadgeAgent: 'Agent',
|
||||||
kindBadgePipeline: 'Pipeline',
|
kindBadgePipeline: 'Pipeline',
|
||||||
|
groupByKind: 'Group by type',
|
||||||
pipelineTypeDescription:
|
pipelineTypeDescription:
|
||||||
'Keep the existing no-code message pipeline for backward compatibility. It only handles message events.',
|
'Keep the existing no-code message pipeline for backward compatibility. It only handles message events.',
|
||||||
allEvents: 'Supports all EBA events',
|
allEvents: 'Supports all EBA events',
|
||||||
|
|||||||
@@ -503,6 +503,7 @@ const jaJP = {
|
|||||||
pipelineType: 'Pipeline',
|
pipelineType: 'Pipeline',
|
||||||
kindBadgeAgent: 'Agent',
|
kindBadgeAgent: 'Agent',
|
||||||
kindBadgePipeline: 'パイプライン',
|
kindBadgePipeline: 'パイプライン',
|
||||||
|
groupByKind: 'タイプ別にグループ化',
|
||||||
pipelineTypeDescription:
|
pipelineTypeDescription:
|
||||||
'既存のノーコードメッセージ Pipeline を互換性のため保持します。メッセージイベントのみ処理できます。',
|
'既存のノーコードメッセージ Pipeline を互換性のため保持します。メッセージイベントのみ処理できます。',
|
||||||
allEvents: 'すべての EBA イベントに対応',
|
allEvents: 'すべての EBA イベントに対応',
|
||||||
|
|||||||
@@ -499,6 +499,7 @@ const zhHans = {
|
|||||||
pipelineType: 'Pipeline',
|
pipelineType: 'Pipeline',
|
||||||
kindBadgeAgent: 'Agent',
|
kindBadgeAgent: 'Agent',
|
||||||
kindBadgePipeline: '流水线',
|
kindBadgePipeline: '流水线',
|
||||||
|
groupByKind: '按类型分组',
|
||||||
pipelineTypeDescription:
|
pipelineTypeDescription:
|
||||||
'保留现有无代码消息流水线,兼容旧配置,只能处理消息事件。',
|
'保留现有无代码消息流水线,兼容旧配置,只能处理消息事件。',
|
||||||
allEvents: '支持全部 EBA 事件',
|
allEvents: '支持全部 EBA 事件',
|
||||||
|
|||||||
Reference in New Issue
Block a user