feat: settings data loading per page (#51)
This commit is contained in:
@@ -1,5 +1,40 @@
|
||||
import { EditorsSettings } from '@/components/settings/editors-settings';
|
||||
import { StateProvider } from '@/components/state-provider';
|
||||
import { fetchUserSettings, listEditors } from '@/lib/http';
|
||||
|
||||
export default function GeneralEditorsPage() {
|
||||
return <EditorsSettings />;
|
||||
export default async function GeneralEditorsPage() {
|
||||
let initialState = {};
|
||||
try {
|
||||
const [editorsResponse, settingsResponse] = await Promise.all([
|
||||
listEditors({ cache: 'no-store' }),
|
||||
fetchUserSettings({ cache: 'no-store' }),
|
||||
]);
|
||||
const settings = settingsResponse.settings;
|
||||
initialState = {
|
||||
editors: {
|
||||
items: editorsResponse.editors ?? [],
|
||||
loaded: true,
|
||||
loading: false,
|
||||
},
|
||||
userSettings: settings
|
||||
? {
|
||||
workspaceId: settings.workspace_id ?? null,
|
||||
boardId: settings.board_id ?? null,
|
||||
repositoryIds: settings.repository_ids ?? [],
|
||||
preferredShell: settings.preferred_shell ?? null,
|
||||
shellOptions: settingsResponse.shell_options ?? [],
|
||||
defaultEditorId: settings.default_editor_id ?? null,
|
||||
loaded: true,
|
||||
}
|
||||
: undefined,
|
||||
};
|
||||
} catch {
|
||||
initialState = {};
|
||||
}
|
||||
|
||||
return (
|
||||
<StateProvider initialState={initialState}>
|
||||
<EditorsSettings />
|
||||
</StateProvider>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,27 @@
|
||||
import { NotificationsSettings } from '@/components/settings/notifications-settings';
|
||||
import { StateProvider } from '@/components/state-provider';
|
||||
import { listNotificationProviders } from '@/lib/http';
|
||||
|
||||
export default function GeneralNotificationsPage() {
|
||||
return <NotificationsSettings />;
|
||||
export default async function GeneralNotificationsPage() {
|
||||
let initialState = {};
|
||||
try {
|
||||
const response = await listNotificationProviders({ cache: 'no-store' });
|
||||
initialState = {
|
||||
notificationProviders: {
|
||||
items: response.providers ?? [],
|
||||
events: response.events ?? [],
|
||||
appriseAvailable: response.apprise_available ?? false,
|
||||
loaded: true,
|
||||
loading: false,
|
||||
},
|
||||
};
|
||||
} catch {
|
||||
initialState = {};
|
||||
}
|
||||
|
||||
return (
|
||||
<StateProvider initialState={initialState}>
|
||||
<NotificationsSettings />
|
||||
</StateProvider>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,32 @@
|
||||
import { GeneralSettings } from '@/components/settings/general-settings';
|
||||
import { StateProvider } from '@/components/state-provider';
|
||||
import { fetchUserSettings } from '@/lib/http';
|
||||
|
||||
export default function GeneralSettingsPage() {
|
||||
return <GeneralSettings />;
|
||||
export default async function GeneralSettingsPage() {
|
||||
let initialState = {};
|
||||
try {
|
||||
const response = await fetchUserSettings({ cache: 'no-store' });
|
||||
const settings = response.settings;
|
||||
initialState = {
|
||||
userSettings: settings
|
||||
? {
|
||||
workspaceId: settings.workspace_id ?? null,
|
||||
boardId: settings.board_id ?? null,
|
||||
repositoryIds: settings.repository_ids ?? [],
|
||||
preferredShell: settings.preferred_shell ?? null,
|
||||
shellOptions: response.shell_options ?? [],
|
||||
defaultEditorId: settings.default_editor_id ?? null,
|
||||
loaded: true,
|
||||
}
|
||||
: undefined,
|
||||
};
|
||||
} catch {
|
||||
initialState = {};
|
||||
}
|
||||
|
||||
return (
|
||||
<StateProvider initialState={initialState}>
|
||||
<GeneralSettings />
|
||||
</StateProvider>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -6,10 +6,6 @@ import {
|
||||
listEnvironments,
|
||||
listExecutors,
|
||||
listWorkspaces,
|
||||
fetchUserSettings,
|
||||
listPrompts,
|
||||
listNotificationProviders,
|
||||
listEditors,
|
||||
} from '@/lib/http';
|
||||
|
||||
export default function SettingsLayout({
|
||||
@@ -31,26 +27,13 @@ async function SettingsLayoutServer({ children }: { children: React.ReactNode })
|
||||
environments,
|
||||
agents,
|
||||
discovery,
|
||||
userSettings,
|
||||
promptsResponse,
|
||||
notificationProviders,
|
||||
editorsResponse,
|
||||
] = await Promise.all([
|
||||
listWorkspaces({ cache: 'no-store' }),
|
||||
listExecutors({ cache: 'no-store' }),
|
||||
listEnvironments({ cache: 'no-store' }),
|
||||
listAgents({ cache: 'no-store' }),
|
||||
listAgentDiscovery({ cache: 'no-store' }),
|
||||
fetchUserSettings({ cache: 'no-store' }).catch(() => null),
|
||||
listPrompts({ cache: 'no-store' }).catch(() => ({ prompts: [] })),
|
||||
listNotificationProviders({ cache: 'no-store' }).catch(() => ({
|
||||
providers: [],
|
||||
apprise_available: false,
|
||||
events: [],
|
||||
})),
|
||||
listEditors({ cache: 'no-store' }).catch(() => ({ editors: [] })),
|
||||
]);
|
||||
const settings = userSettings?.settings;
|
||||
initialState = {
|
||||
workspaces: {
|
||||
items: workspaces.workspaces.map((workspace) => ({
|
||||
@@ -88,32 +71,6 @@ async function SettingsLayoutServer({ children }: { children: React.ReactNode })
|
||||
environmentsLoaded: true,
|
||||
agentsLoaded: true,
|
||||
},
|
||||
prompts: {
|
||||
items: promptsResponse.prompts ?? [],
|
||||
loaded: true,
|
||||
loading: false,
|
||||
},
|
||||
editors: {
|
||||
items: editorsResponse.editors ?? [],
|
||||
loaded: true,
|
||||
loading: false,
|
||||
},
|
||||
notificationProviders: {
|
||||
items: notificationProviders.providers ?? [],
|
||||
events: notificationProviders.events ?? [],
|
||||
appriseAvailable: notificationProviders.apprise_available ?? false,
|
||||
loaded: true,
|
||||
loading: false,
|
||||
},
|
||||
userSettings: {
|
||||
workspaceId: settings?.workspace_id ?? null,
|
||||
boardId: settings?.board_id ?? null,
|
||||
repositoryIds: settings?.repository_ids ?? [],
|
||||
preferredShell: settings?.preferred_shell ?? null,
|
||||
shellOptions: userSettings?.shell_options ?? [],
|
||||
defaultEditorId: settings?.default_editor_id ?? null,
|
||||
loaded: Boolean(settings),
|
||||
},
|
||||
};
|
||||
} catch {
|
||||
initialState = {};
|
||||
|
||||
@@ -1,5 +1,25 @@
|
||||
import { PromptsSettings } from '@/components/settings/prompts-settings';
|
||||
import { StateProvider } from '@/components/state-provider';
|
||||
import { listPrompts } from '@/lib/http';
|
||||
|
||||
export default async function PromptsSettingsPage() {
|
||||
return <PromptsSettings />;
|
||||
let initialState = {};
|
||||
try {
|
||||
const response = await listPrompts({ cache: 'no-store' });
|
||||
initialState = {
|
||||
prompts: {
|
||||
items: response.prompts ?? [],
|
||||
loaded: true,
|
||||
loading: false,
|
||||
},
|
||||
};
|
||||
} catch {
|
||||
initialState = {};
|
||||
}
|
||||
|
||||
return (
|
||||
<StateProvider initialState={initialState}>
|
||||
<PromptsSettings />
|
||||
</StateProvider>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use client';
|
||||
|
||||
import { useMemo, useState, type FormEvent } from 'react';
|
||||
import { useMemo, useReducer, useState, useSyncExternalStore, type FormEvent } from 'react';
|
||||
import { IconBell, IconRefresh } from '@tabler/icons-react';
|
||||
import { Button } from '@kandev/ui/button';
|
||||
import { Checkbox } from '@kandev/ui/checkbox';
|
||||
@@ -105,14 +105,17 @@ export function NotificationsSettings() {
|
||||
const [appriseFormMode, setAppriseFormMode] = useState<AppriseFormMode>('create');
|
||||
const [activeAppriseId, setActiveAppriseId] = useState<string | null>(null);
|
||||
const [pendingDeletes, setPendingDeletes] = useState<Set<string>>(new Set());
|
||||
const [notificationPermission, setNotificationPermission] = useState<
|
||||
NotificationPermission | 'unsupported'
|
||||
>(() => {
|
||||
if (typeof Notification === 'undefined') {
|
||||
return 'unsupported';
|
||||
}
|
||||
return Notification.permission;
|
||||
});
|
||||
const mounted = useSyncExternalStore(
|
||||
() => () => undefined,
|
||||
() => true,
|
||||
() => false
|
||||
);
|
||||
const [, bumpPermission] = useReducer((value) => value + 1, 0);
|
||||
const notificationPermission: NotificationPermission | 'unsupported' = !mounted
|
||||
? 'default'
|
||||
: typeof Notification === 'undefined'
|
||||
? 'unsupported'
|
||||
: Notification.permission;
|
||||
|
||||
const saveRequest = useRequest(async () => {
|
||||
const updates: Array<Promise<NotificationProvider>> = [];
|
||||
@@ -215,19 +218,17 @@ export function NotificationsSettings() {
|
||||
|
||||
const handleRequestPermission = async () => {
|
||||
if (typeof Notification === 'undefined') {
|
||||
setNotificationPermission('unsupported');
|
||||
return;
|
||||
}
|
||||
const result = await Notification.requestPermission();
|
||||
setNotificationPermission(result);
|
||||
await Notification.requestPermission();
|
||||
bumpPermission();
|
||||
};
|
||||
|
||||
const handleRefreshPermission = () => {
|
||||
if (typeof Notification === 'undefined') {
|
||||
setNotificationPermission('unsupported');
|
||||
return;
|
||||
}
|
||||
setNotificationPermission(Notification.permission);
|
||||
bumpPermission();
|
||||
};
|
||||
|
||||
const handleTestNotification = async () => {
|
||||
@@ -237,7 +238,7 @@ export function NotificationsSettings() {
|
||||
let permission = Notification.permission;
|
||||
if (permission === 'default') {
|
||||
permission = await Notification.requestPermission();
|
||||
setNotificationPermission(permission);
|
||||
bumpPermission();
|
||||
}
|
||||
if (permission !== 'granted') {
|
||||
return;
|
||||
|
||||
@@ -28,6 +28,7 @@ import {
|
||||
SidebarMenuAction,
|
||||
SidebarHeader,
|
||||
} from '@kandev/ui/sidebar';
|
||||
import { ScrollArea } from '@kandev/ui/scroll-area';
|
||||
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@kandev/ui/collapsible';
|
||||
import { useAppStore } from '@/components/state-provider';
|
||||
import { useAvailableAgents } from '@/hooks/use-available-agents';
|
||||
@@ -59,245 +60,187 @@ export function SettingsAppSidebar() {
|
||||
</SidebarMenuItem>
|
||||
</SidebarMenu>
|
||||
</SidebarHeader>
|
||||
<SidebarContent>
|
||||
<SidebarGroup>
|
||||
<SidebarGroupLabel>Settings</SidebarGroupLabel>
|
||||
<SidebarGroupContent>
|
||||
<SidebarMenu>
|
||||
<SidebarContent className="overflow-hidden">
|
||||
<ScrollArea className="h-full" type="always">
|
||||
<SidebarGroup>
|
||||
<SidebarGroupLabel>Settings</SidebarGroupLabel>
|
||||
<SidebarGroupContent>
|
||||
<SidebarMenu>
|
||||
{/* General */}
|
||||
<Collapsible defaultOpen className="group/collapsible">
|
||||
<SidebarMenuItem>
|
||||
<SidebarMenuButton asChild tooltip="General">
|
||||
<Link href="/settings/general">
|
||||
<IconSettings className="h-4 w-4" />
|
||||
<span>General</span>
|
||||
</Link>
|
||||
</SidebarMenuButton>
|
||||
<CollapsibleTrigger asChild>
|
||||
<SidebarMenuAction className="data-[state=open]:rotate-90">
|
||||
<IconChevronRight className="h-4 w-4" />
|
||||
<span className="sr-only">Toggle</span>
|
||||
</SidebarMenuAction>
|
||||
</CollapsibleTrigger>
|
||||
<CollapsibleContent>
|
||||
<SidebarMenuSub>
|
||||
<SidebarMenuSubItem>
|
||||
<SidebarMenuSubButton
|
||||
asChild
|
||||
size="sm"
|
||||
isActive={pathname === '/settings/general/notifications'}
|
||||
>
|
||||
<Link href="/settings/general/notifications">
|
||||
<IconBell className="h-4 w-4" />
|
||||
<span>Notifications</span>
|
||||
</Link>
|
||||
</SidebarMenuSubButton>
|
||||
</SidebarMenuSubItem>
|
||||
<SidebarMenuSubItem>
|
||||
<SidebarMenuSubButton
|
||||
asChild
|
||||
size="sm"
|
||||
isActive={pathname === '/settings/general/editors'}
|
||||
>
|
||||
<Link href="/settings/general/editors">
|
||||
<IconCode className="h-4 w-4" />
|
||||
<span>Editors</span>
|
||||
</Link>
|
||||
</SidebarMenuSubButton>
|
||||
</SidebarMenuSubItem>
|
||||
</SidebarMenuSub>
|
||||
</CollapsibleContent>
|
||||
</SidebarMenuItem>
|
||||
</Collapsible>
|
||||
<SidebarMenuItem>
|
||||
<SidebarMenuButton asChild tooltip="General">
|
||||
<Link href="/settings/general">
|
||||
<IconSettings className="h-4 w-4" />
|
||||
<span>General</span>
|
||||
</Link>
|
||||
</SidebarMenuButton>
|
||||
<SidebarMenuSub className="ml-3 mt-1">
|
||||
<SidebarMenuSubItem>
|
||||
<SidebarMenuSubButton
|
||||
asChild
|
||||
size="sm"
|
||||
isActive={pathname === '/settings/general/notifications'}
|
||||
>
|
||||
<Link href="/settings/general/notifications">
|
||||
<IconBell className="h-4 w-4" />
|
||||
<span>Notifications</span>
|
||||
</Link>
|
||||
</SidebarMenuSubButton>
|
||||
</SidebarMenuSubItem>
|
||||
<SidebarMenuSubItem>
|
||||
<SidebarMenuSubButton
|
||||
asChild
|
||||
size="sm"
|
||||
isActive={pathname === '/settings/general/editors'}
|
||||
>
|
||||
<Link href="/settings/general/editors">
|
||||
<IconCode className="h-4 w-4" />
|
||||
<span>Editors</span>
|
||||
</Link>
|
||||
</SidebarMenuSubButton>
|
||||
</SidebarMenuSubItem>
|
||||
</SidebarMenuSub>
|
||||
</SidebarMenuItem>
|
||||
|
||||
{/* Workspaces */}
|
||||
<Collapsible defaultOpen className="group/collapsible">
|
||||
<SidebarMenuItem>
|
||||
<SidebarMenuButton asChild tooltip="Workspaces">
|
||||
<Link href="/settings/workspace">
|
||||
<IconFolder className="h-4 w-4" />
|
||||
<span>Workspaces</span>
|
||||
</Link>
|
||||
</SidebarMenuButton>
|
||||
{workspaces.length > 0 && (
|
||||
<>
|
||||
<CollapsibleTrigger asChild>
|
||||
<SidebarMenuAction className="data-[state=open]:rotate-90">
|
||||
<IconChevronRight className="h-4 w-4" />
|
||||
<span className="sr-only">Toggle</span>
|
||||
</SidebarMenuAction>
|
||||
</CollapsibleTrigger>
|
||||
<CollapsibleContent>
|
||||
<SidebarMenuSub>
|
||||
{workspaces.map((workspace) => {
|
||||
const workspacePath = `/settings/workspace/${workspace.id}`;
|
||||
const boardsPath = `${workspacePath}/boards`;
|
||||
const repositoriesPath = `${workspacePath}/repositories`;
|
||||
<SidebarMenuItem>
|
||||
<SidebarMenuButton asChild tooltip="Workspaces">
|
||||
<Link href="/settings/workspace">
|
||||
<IconFolder className="h-4 w-4" />
|
||||
<span>Workspaces</span>
|
||||
</Link>
|
||||
</SidebarMenuButton>
|
||||
{workspaces.length > 0 && (
|
||||
<SidebarMenuSub className="ml-3 mt-1">
|
||||
{workspaces.map((workspace) => {
|
||||
const workspacePath = `/settings/workspace/${workspace.id}`;
|
||||
const boardsPath = `${workspacePath}/boards`;
|
||||
const repositoriesPath = `${workspacePath}/repositories`;
|
||||
|
||||
return (
|
||||
<SidebarMenuSubItem key={workspace.id}>
|
||||
<SidebarMenuSubButton
|
||||
asChild
|
||||
isActive={pathname === workspacePath}
|
||||
>
|
||||
<Link href={workspacePath}>
|
||||
<span>{workspace.name}</span>
|
||||
</Link>
|
||||
</SidebarMenuSubButton>
|
||||
<SidebarMenuSub className="ml-3">
|
||||
<SidebarMenuSubItem>
|
||||
<SidebarMenuSubButton
|
||||
asChild
|
||||
size="sm"
|
||||
isActive={pathname === repositoriesPath}
|
||||
>
|
||||
<Link href={repositoriesPath}>
|
||||
<span>Repositories</span>
|
||||
</Link>
|
||||
</SidebarMenuSubButton>
|
||||
</SidebarMenuSubItem>
|
||||
<SidebarMenuSubItem>
|
||||
<SidebarMenuSubButton
|
||||
asChild
|
||||
size="sm"
|
||||
isActive={pathname === boardsPath}
|
||||
>
|
||||
<Link href={boardsPath}>
|
||||
<span>Boards</span>
|
||||
</Link>
|
||||
</SidebarMenuSubButton>
|
||||
</SidebarMenuSubItem>
|
||||
</SidebarMenuSub>
|
||||
</SidebarMenuSubItem>
|
||||
);
|
||||
})}
|
||||
</SidebarMenuSub>
|
||||
</CollapsibleContent>
|
||||
</>
|
||||
)}
|
||||
</SidebarMenuItem>
|
||||
</Collapsible>
|
||||
return (
|
||||
<SidebarMenuSubItem key={workspace.id}>
|
||||
<SidebarMenuSubButton
|
||||
asChild
|
||||
isActive={pathname === workspacePath}
|
||||
>
|
||||
<Link href={workspacePath}>
|
||||
<span>{workspace.name}</span>
|
||||
</Link>
|
||||
</SidebarMenuSubButton>
|
||||
<SidebarMenuSub className="ml-3">
|
||||
<SidebarMenuSubItem>
|
||||
<SidebarMenuSubButton
|
||||
asChild
|
||||
size="sm"
|
||||
isActive={pathname === repositoriesPath}
|
||||
>
|
||||
<Link href={repositoriesPath}>
|
||||
<span>Repositories</span>
|
||||
</Link>
|
||||
</SidebarMenuSubButton>
|
||||
</SidebarMenuSubItem>
|
||||
<SidebarMenuSubItem>
|
||||
<SidebarMenuSubButton
|
||||
asChild
|
||||
size="sm"
|
||||
isActive={pathname === boardsPath}
|
||||
>
|
||||
<Link href={boardsPath}>
|
||||
<span>Boards</span>
|
||||
</Link>
|
||||
</SidebarMenuSubButton>
|
||||
</SidebarMenuSubItem>
|
||||
</SidebarMenuSub>
|
||||
</SidebarMenuSubItem>
|
||||
);
|
||||
})}
|
||||
</SidebarMenuSub>
|
||||
)}
|
||||
</SidebarMenuItem>
|
||||
|
||||
{/* Environments */}
|
||||
<Collapsible defaultOpen className="group/collapsible">
|
||||
<SidebarMenuItem>
|
||||
<SidebarMenuButton asChild tooltip="Environments">
|
||||
<Link href="/settings/environments">
|
||||
<IconServer className="h-4 w-4" />
|
||||
<span>Environments</span>
|
||||
</Link>
|
||||
</SidebarMenuButton>
|
||||
{environments.length > 0 && (
|
||||
<>
|
||||
<CollapsibleTrigger asChild>
|
||||
<SidebarMenuAction className="data-[state=open]:rotate-90">
|
||||
<IconChevronRight className="h-4 w-4" />
|
||||
<span className="sr-only">Toggle</span>
|
||||
</SidebarMenuAction>
|
||||
</CollapsibleTrigger>
|
||||
<CollapsibleContent>
|
||||
<SidebarMenuSub>
|
||||
{environments.map((env) => (
|
||||
<SidebarMenuSubItem key={env.id}>
|
||||
<SidebarMenuSubButton
|
||||
asChild
|
||||
isActive={pathname === `/settings/environment/${env.id}`}
|
||||
>
|
||||
<Link href={`/settings/environment/${env.id}`}>
|
||||
<span>{env.name}</span>
|
||||
</Link>
|
||||
</SidebarMenuSubButton>
|
||||
</SidebarMenuSubItem>
|
||||
))}
|
||||
</SidebarMenuSub>
|
||||
</CollapsibleContent>
|
||||
</>
|
||||
)}
|
||||
</SidebarMenuItem>
|
||||
</Collapsible>
|
||||
<SidebarMenuItem>
|
||||
<SidebarMenuButton asChild tooltip="Environments">
|
||||
<Link href="/settings/environments">
|
||||
<IconServer className="h-4 w-4" />
|
||||
<span>Environments</span>
|
||||
</Link>
|
||||
</SidebarMenuButton>
|
||||
{environments.length > 0 && (
|
||||
<SidebarMenuSub className="ml-3 mt-1">
|
||||
{environments.map((env) => (
|
||||
<SidebarMenuSubItem key={env.id}>
|
||||
<SidebarMenuSubButton
|
||||
asChild
|
||||
isActive={pathname === `/settings/environment/${env.id}`}
|
||||
>
|
||||
<Link href={`/settings/environment/${env.id}`}>
|
||||
<span>{env.name}</span>
|
||||
</Link>
|
||||
</SidebarMenuSubButton>
|
||||
</SidebarMenuSubItem>
|
||||
))}
|
||||
</SidebarMenuSub>
|
||||
)}
|
||||
</SidebarMenuItem>
|
||||
|
||||
{/* Executors */}
|
||||
<Collapsible defaultOpen className="group/collapsible">
|
||||
<SidebarMenuItem>
|
||||
<SidebarMenuButton asChild tooltip="Executors">
|
||||
<Link href="/settings/executors">
|
||||
<IconCpu className="h-4 w-4" />
|
||||
<span>Executors</span>
|
||||
</Link>
|
||||
</SidebarMenuButton>
|
||||
{executors.length > 0 && (
|
||||
<>
|
||||
<CollapsibleTrigger asChild>
|
||||
<SidebarMenuAction className="data-[state=open]:rotate-90">
|
||||
<IconChevronRight className="h-4 w-4" />
|
||||
<span className="sr-only">Toggle</span>
|
||||
</SidebarMenuAction>
|
||||
</CollapsibleTrigger>
|
||||
<CollapsibleContent>
|
||||
<SidebarMenuSub>
|
||||
{executors.map((executor) => (
|
||||
<SidebarMenuSubItem key={executor.id}>
|
||||
<SidebarMenuSubButton
|
||||
asChild
|
||||
isActive={pathname === `/settings/executor/${executor.id}`}
|
||||
>
|
||||
<Link href={`/settings/executor/${executor.id}`}>
|
||||
<span>{executor.name}</span>
|
||||
</Link>
|
||||
</SidebarMenuSubButton>
|
||||
</SidebarMenuSubItem>
|
||||
))}
|
||||
</SidebarMenuSub>
|
||||
</CollapsibleContent>
|
||||
</>
|
||||
)}
|
||||
</SidebarMenuItem>
|
||||
</Collapsible>
|
||||
<SidebarMenuItem>
|
||||
<SidebarMenuButton asChild tooltip="Executors">
|
||||
<Link href="/settings/executors">
|
||||
<IconCpu className="h-4 w-4" />
|
||||
<span>Executors</span>
|
||||
</Link>
|
||||
</SidebarMenuButton>
|
||||
{executors.length > 0 && (
|
||||
<SidebarMenuSub className="ml-3 mt-1">
|
||||
{executors.map((executor) => (
|
||||
<SidebarMenuSubItem key={executor.id}>
|
||||
<SidebarMenuSubButton
|
||||
asChild
|
||||
isActive={pathname === `/settings/executor/${executor.id}`}
|
||||
>
|
||||
<Link href={`/settings/executor/${executor.id}`}>
|
||||
<span>{executor.name}</span>
|
||||
</Link>
|
||||
</SidebarMenuSubButton>
|
||||
</SidebarMenuSubItem>
|
||||
))}
|
||||
</SidebarMenuSub>
|
||||
)}
|
||||
</SidebarMenuItem>
|
||||
|
||||
{/* Agents */}
|
||||
<Collapsible defaultOpen className="group/collapsible">
|
||||
<SidebarMenuItem>
|
||||
<SidebarMenuButton asChild tooltip="Agents">
|
||||
<Link href="/settings/agents">
|
||||
<IconRobot className="h-4 w-4" />
|
||||
<span>Agents</span>
|
||||
</Link>
|
||||
</SidebarMenuButton>
|
||||
{agents.length > 0 && (
|
||||
<>
|
||||
<CollapsibleTrigger asChild>
|
||||
<SidebarMenuAction className="data-[state=open]:rotate-90">
|
||||
<IconChevronRight className="h-4 w-4" />
|
||||
<span className="sr-only">Toggle</span>
|
||||
</SidebarMenuAction>
|
||||
</CollapsibleTrigger>
|
||||
<CollapsibleContent>
|
||||
<SidebarMenuSub>
|
||||
{agents.flatMap((agent) =>
|
||||
agent.profiles.map((profile) => {
|
||||
const encodedAgent = encodeURIComponent(agent.name);
|
||||
const profilePath = `/settings/agents/${encodedAgent}/profiles/${profile.id}`;
|
||||
const agentLabel = profile.agent_display_name || agent.name;
|
||||
return (
|
||||
<SidebarMenuSubItem key={profile.id}>
|
||||
<SidebarMenuSubButton
|
||||
asChild
|
||||
isActive={pathname === profilePath}
|
||||
>
|
||||
<Link href={profilePath}>
|
||||
<span>{agentLabel} • {profile.name}</span>
|
||||
</Link>
|
||||
</SidebarMenuSubButton>
|
||||
</SidebarMenuSubItem>
|
||||
);
|
||||
})
|
||||
)}
|
||||
</SidebarMenuSub>
|
||||
</CollapsibleContent>
|
||||
</>
|
||||
)}
|
||||
</SidebarMenuItem>
|
||||
</Collapsible>
|
||||
<SidebarMenuItem>
|
||||
<SidebarMenuButton asChild tooltip="Agents">
|
||||
<Link href="/settings/agents">
|
||||
<IconRobot className="h-4 w-4" />
|
||||
<span>Agents</span>
|
||||
</Link>
|
||||
</SidebarMenuButton>
|
||||
{agents.length > 0 && (
|
||||
<SidebarMenuSub className="ml-3 mt-1">
|
||||
{agents.flatMap((agent) =>
|
||||
agent.profiles.map((profile) => {
|
||||
const encodedAgent = encodeURIComponent(agent.name);
|
||||
const profilePath = `/settings/agents/${encodedAgent}/profiles/${profile.id}`;
|
||||
const agentLabel = profile.agent_display_name || agent.name;
|
||||
return (
|
||||
<SidebarMenuSubItem key={profile.id}>
|
||||
<SidebarMenuSubButton asChild isActive={pathname === profilePath}>
|
||||
<Link href={profilePath}>
|
||||
<span>
|
||||
{agentLabel} • {profile.name}
|
||||
</span>
|
||||
</Link>
|
||||
</SidebarMenuSubButton>
|
||||
</SidebarMenuSubItem>
|
||||
);
|
||||
})
|
||||
)}
|
||||
</SidebarMenuSub>
|
||||
)}
|
||||
</SidebarMenuItem>
|
||||
|
||||
{/* Prompts */}
|
||||
<SidebarMenuItem>
|
||||
@@ -308,9 +251,10 @@ export function SettingsAppSidebar() {
|
||||
</Link>
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem>
|
||||
</SidebarMenu>
|
||||
</SidebarGroupContent>
|
||||
</SidebarGroup>
|
||||
</SidebarMenu>
|
||||
</SidebarGroupContent>
|
||||
</SidebarGroup>
|
||||
</ScrollArea>
|
||||
</SidebarContent>
|
||||
</Sidebar>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user