mirror of
https://github.com/vxcontrol/pentagi.git
synced 2026-08-26 13:06:32 +00:00
refactor(ui): replace custom StatusCard with the shadcn Empty primitive
Consolidate every empty / loading / error / not-found / error-boundary state off the bespoke StatusCard onto the canonical shadcn Empty (already used by DataTable) and delete components/ui/status-card.tsx. Empty renders borderless and centered, so the wide-viewport "letterbox" look is fixed at the root instead of capping a solid card. Also in this pass: - DataTable: truncate column headers so plain-string headers in narrow fixed-size columns (e.g. "System Prompt") stop wrapping to two lines. - Settings > Providers / API Tokens: move the Create action into the page header (icon-collapses on mobile) and the API-token Playground/Swagger links into a header overflow menu; drop the cramped description row that needed the earlier truncate/flex-wrap workarounds. Those links use a relative href since the API base path is root-relative. - Settings > Prompts: drop the fixed size on the Name column of both tables so it flexes to fill (matching Providers) and truncates. - Settings > Account: keep "Member since" on one line at mobile widths (min-w-0 + truncate, badge shrink-0). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
001c6c0d9d
commit
a3dcd053bc
@@ -3,7 +3,7 @@ import { useEffect } from 'react';
|
||||
import { useRouteError } from 'react-router-dom';
|
||||
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { StatusCard } from '@/components/ui/status-card';
|
||||
import { Empty, EmptyContent, EmptyDescription, EmptyHeader, EmptyMedia, EmptyTitle } from '@/components/ui/empty';
|
||||
import { isChunkLoadError, isDomDesyncError, reloadOnce } from '@/lib/chunk-reload';
|
||||
|
||||
/**
|
||||
@@ -29,30 +29,29 @@ function RouteErrorBoundary() {
|
||||
className="grid min-h-svh w-full place-items-center p-4"
|
||||
role="alert"
|
||||
>
|
||||
<StatusCard
|
||||
action={
|
||||
<Empty>
|
||||
<EmptyHeader>
|
||||
<EmptyMedia variant="icon">
|
||||
<TriangleAlert />
|
||||
</EmptyMedia>
|
||||
<EmptyTitle>Something went wrong</EmptyTitle>
|
||||
<EmptyDescription>
|
||||
{isChunk
|
||||
? 'A new version was likely just deployed. Reloading will load the latest one.'
|
||||
: isDesync
|
||||
? 'The page hit a display glitch. Reloading usually clears it.'
|
||||
: 'The page ran into an unexpected error. Reloading usually clears it.'}
|
||||
</EmptyDescription>
|
||||
</EmptyHeader>
|
||||
<EmptyContent>
|
||||
<Button
|
||||
onClick={() => window.location.reload()}
|
||||
variant="secondary"
|
||||
>
|
||||
Reload
|
||||
</Button>
|
||||
}
|
||||
description={
|
||||
isChunk
|
||||
? 'A new version was likely just deployed. Reloading will load the latest one.'
|
||||
: isDesync
|
||||
? 'The page hit a display glitch. Reloading usually clears it.'
|
||||
: 'The page ran into an unexpected error. Reloading usually clears it.'
|
||||
}
|
||||
icon={
|
||||
<TriangleAlert
|
||||
aria-hidden
|
||||
className="text-muted-foreground size-10"
|
||||
/>
|
||||
}
|
||||
title="Something went wrong"
|
||||
/>
|
||||
</EmptyContent>
|
||||
</Empty>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -764,7 +764,7 @@ function DataTable<TData, TValue = unknown>({
|
||||
>
|
||||
{headerGroup.headers.map((header) => (
|
||||
<TableHead
|
||||
className={header.column.columnDef.meta?.headerClassName}
|
||||
className={cn('truncate', header.column.columnDef.meta?.headerClassName)}
|
||||
key={header.id}
|
||||
style={
|
||||
header.column.columnDef.size
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
import { type ReactNode } from 'react';
|
||||
|
||||
import { Card, CardContent } from '@/components/ui/card';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
interface StatusCardProps {
|
||||
action?: ReactNode;
|
||||
className?: string;
|
||||
description?: ReactNode;
|
||||
icon?: ReactNode;
|
||||
title: ReactNode;
|
||||
}
|
||||
|
||||
export function StatusCard({ action, className, description, icon, title }: StatusCardProps) {
|
||||
return (
|
||||
<Card className={cn('', className)}>
|
||||
<CardContent className="flex flex-col items-center justify-center px-4 py-8 text-center">
|
||||
{icon && <div className="mb-4 flex items-center justify-center">{icon}</div>}
|
||||
<h3 className="text-foreground text-lg font-semibold">{title}</h3>
|
||||
{description && <div className="text-muted-foreground mt-2 max-w-sm text-sm">{description}</div>}
|
||||
{action && <div className="mt-4">{action}</div>}
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
@@ -29,7 +29,7 @@ import {
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from '@/components/ui/dropdown-menu';
|
||||
import { StatusCard } from '@/components/ui/status-card';
|
||||
import { Empty, EmptyContent, EmptyDescription, EmptyHeader, EmptyMedia, EmptyTitle } from '@/components/ui/empty';
|
||||
import { Toggle } from '@/components/ui/toggle';
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
|
||||
import { RenameFlowDocument, ResultType, StatusType, type TerminalFragmentFragment } from '@/graphql/types';
|
||||
@@ -589,11 +589,17 @@ function Flows() {
|
||||
<>
|
||||
{pageHeader}
|
||||
<div className="flex flex-col gap-4 p-4">
|
||||
<StatusCard
|
||||
description="Please wait while we fetch your conversation flows"
|
||||
icon={<Loader2 className="text-muted-foreground size-16 animate-spin" />}
|
||||
title="Loading flows..."
|
||||
/>
|
||||
<Empty>
|
||||
<EmptyHeader>
|
||||
<EmptyMedia>
|
||||
<Loader2 className="text-muted-foreground size-10 animate-spin" />
|
||||
</EmptyMedia>
|
||||
<EmptyTitle>Loading flows...</EmptyTitle>
|
||||
<EmptyDescription>
|
||||
Please wait while we fetch your conversation flows
|
||||
</EmptyDescription>
|
||||
</EmptyHeader>
|
||||
</Empty>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
@@ -604,8 +610,15 @@ function Flows() {
|
||||
<>
|
||||
{pageHeader}
|
||||
<div className="flex flex-col gap-4 p-4">
|
||||
<StatusCard
|
||||
action={
|
||||
<Empty>
|
||||
<EmptyHeader>
|
||||
<EmptyMedia variant="icon">
|
||||
<GitFork />
|
||||
</EmptyMedia>
|
||||
<EmptyTitle>No flows found</EmptyTitle>
|
||||
<EmptyDescription>Get started by creating your first conversation flow</EmptyDescription>
|
||||
</EmptyHeader>
|
||||
<EmptyContent>
|
||||
<Button
|
||||
onClick={() => navigate(routes.newFlow)}
|
||||
variant="secondary"
|
||||
@@ -613,11 +626,8 @@ function Flows() {
|
||||
<Plus />
|
||||
New Flow
|
||||
</Button>
|
||||
}
|
||||
description="Get started by creating your first conversation flow"
|
||||
icon={<GitFork className="text-muted-foreground size-8" />}
|
||||
title="No flows found"
|
||||
/>
|
||||
</EmptyContent>
|
||||
</Empty>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -27,8 +27,8 @@ import {
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from '@/components/ui/dropdown-menu';
|
||||
import { Empty, EmptyContent, EmptyDescription, EmptyHeader, EmptyMedia, EmptyTitle } from '@/components/ui/empty';
|
||||
import { InputSearch } from '@/components/ui/input-search';
|
||||
import { StatusCard } from '@/components/ui/status-card';
|
||||
import { KnowledgeDocType } from '@/graphql/types';
|
||||
import { useTableState } from '@/hooks/use-table-state';
|
||||
import { routes } from '@/lib/routes';
|
||||
@@ -404,11 +404,17 @@ function Knowledges() {
|
||||
<>
|
||||
{pageHeader}
|
||||
<div className="flex flex-col gap-4 p-4">
|
||||
<StatusCard
|
||||
description="Please wait while we fetch your knowledge documents"
|
||||
icon={<Loader2 className="text-muted-foreground size-16 animate-spin" />}
|
||||
title="Loading knowledges..."
|
||||
/>
|
||||
<Empty>
|
||||
<EmptyHeader>
|
||||
<EmptyMedia>
|
||||
<Loader2 className="text-muted-foreground size-10 animate-spin" />
|
||||
</EmptyMedia>
|
||||
<EmptyTitle>Loading knowledges...</EmptyTitle>
|
||||
<EmptyDescription>
|
||||
Please wait while we fetch your knowledge documents
|
||||
</EmptyDescription>
|
||||
</EmptyHeader>
|
||||
</Empty>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
@@ -419,8 +425,17 @@ function Knowledges() {
|
||||
<>
|
||||
{pageHeader}
|
||||
<div className="flex flex-col gap-4 p-4">
|
||||
<StatusCard
|
||||
action={
|
||||
<Empty>
|
||||
<EmptyHeader>
|
||||
<EmptyMedia variant="icon">
|
||||
<LibraryBig />
|
||||
</EmptyMedia>
|
||||
<EmptyTitle>No knowledge documents yet</EmptyTitle>
|
||||
<EmptyDescription>
|
||||
Create your first knowledge document to enrich the vector store
|
||||
</EmptyDescription>
|
||||
</EmptyHeader>
|
||||
<EmptyContent>
|
||||
<Button
|
||||
onClick={() => navigate(routes.newKnowledge)}
|
||||
variant="secondary"
|
||||
@@ -428,11 +443,8 @@ function Knowledges() {
|
||||
<Plus />
|
||||
New Knowledge
|
||||
</Button>
|
||||
}
|
||||
description="Create your first knowledge document to enrich the vector store"
|
||||
icon={<LibraryBig className="text-muted-foreground size-8" />}
|
||||
title="No knowledge documents yet"
|
||||
/>
|
||||
</EmptyContent>
|
||||
</Empty>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -62,11 +62,11 @@ function SettingsAccount() {
|
||||
<div className="bg-muted text-foreground flex size-12 shrink-0 items-center justify-center rounded-lg text-lg font-semibold">
|
||||
{initial}
|
||||
</div>
|
||||
<div className="flex flex-1 flex-col gap-0.5">
|
||||
<div className="flex min-w-0 flex-1 flex-col gap-0.5">
|
||||
<CardTitle className="truncate">{displayName}</CardTitle>
|
||||
{memberSince && <CardDescription>Member since {memberSince}</CardDescription>}
|
||||
{memberSince && <CardDescription className="truncate">Member since {memberSince}</CardDescription>}
|
||||
</div>
|
||||
<Badge variant="secondary">{accountLabel}</Badge>
|
||||
<Badge className="shrink-0" variant="secondary">{accountLabel}</Badge>
|
||||
</CardHeader>
|
||||
</Card>
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ import * as z from 'zod';
|
||||
|
||||
import type { ApiTokenFragmentFragment } from '@/graphql/types';
|
||||
|
||||
import { AppHeader, AppHeaderContent, AppHeaderTitle } from '@/components/layouts/app/app-header';
|
||||
import { AppHeader, AppHeaderAction, AppHeaderActions, AppHeaderContent, AppHeaderTitle } from '@/components/layouts/app/app-header';
|
||||
import ConfirmationDialog from '@/components/shared/confirmation-dialog';
|
||||
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
@@ -40,10 +40,10 @@ import {
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from '@/components/ui/dropdown-menu';
|
||||
import { Empty, EmptyContent, EmptyDescription, EmptyHeader, EmptyMedia, EmptyTitle } from '@/components/ui/empty';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
|
||||
import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
|
||||
import { StatusCard } from '@/components/ui/status-card';
|
||||
import {
|
||||
ApiTokenCreatedDocument,
|
||||
ApiTokenDeletedDocument,
|
||||
@@ -144,45 +144,6 @@ const copyToClipboard = async (text: string): Promise<boolean> => {
|
||||
}
|
||||
};
|
||||
|
||||
function SettingsAPITokensHeader({ onCreateClick }: { onCreateClick: () => void }) {
|
||||
return (
|
||||
<div className="flex items-center justify-between gap-4">
|
||||
<div className="flex min-w-0 flex-1 flex-col gap-2">
|
||||
<p className="text-muted-foreground truncate">Manage API tokens for programmatic access</p>
|
||||
<div className="flex gap-4 text-sm">
|
||||
<a
|
||||
className="text-primary inline-flex items-center gap-1 underline hover:no-underline"
|
||||
href={`${window.location.origin}${baseUrl}/graphql/playground`}
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
GraphQL Playground
|
||||
<ExternalLink className="size-3" />
|
||||
</a>
|
||||
<a
|
||||
className="text-primary inline-flex items-center gap-1 underline hover:no-underline"
|
||||
href={`${window.location.origin}${baseUrl}/swagger/index.html`}
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
Swagger UI
|
||||
<ExternalLink className="size-3" />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Button
|
||||
className="shrink-0"
|
||||
onClick={onCreateClick}
|
||||
variant="secondary"
|
||||
>
|
||||
<Plus className="size-4" />
|
||||
Create Token
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const createNewTokenPlaceholder: APIToken = {
|
||||
createdAt: new Date().toISOString(),
|
||||
id: 'create-new',
|
||||
@@ -848,6 +809,51 @@ function SettingsAPITokens() {
|
||||
<AppHeaderContent>
|
||||
<AppHeaderTitle icon={<Key className="size-4 shrink-0" />}>API Tokens</AppHeaderTitle>
|
||||
</AppHeaderContent>
|
||||
<AppHeaderActions>
|
||||
<AppHeaderAction
|
||||
icon={<Plus />}
|
||||
label="Create Token"
|
||||
onClick={handleCreateNew}
|
||||
variant="secondary"
|
||||
/>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
aria-label="Developer tools"
|
||||
className="size-8 p-0"
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
>
|
||||
<Ellipsis />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent
|
||||
align="end"
|
||||
className="min-w-44"
|
||||
>
|
||||
<DropdownMenuItem asChild>
|
||||
<a
|
||||
href={`${baseUrl}/graphql/playground`}
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
<ExternalLink className="size-4" />
|
||||
GraphQL Playground
|
||||
</a>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem asChild>
|
||||
<a
|
||||
href={`${baseUrl}/swagger/index.html`}
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
<ExternalLink className="size-4" />
|
||||
Swagger UI
|
||||
</a>
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</AppHeaderActions>
|
||||
</AppHeader>
|
||||
);
|
||||
|
||||
@@ -856,12 +862,15 @@ function SettingsAPITokens() {
|
||||
<>
|
||||
{pageHeader}
|
||||
<div className="flex flex-1 flex-col gap-4 p-4">
|
||||
<SettingsAPITokensHeader onCreateClick={handleCreateNew} />
|
||||
<StatusCard
|
||||
description="Please wait while we fetch your API tokens"
|
||||
icon={<Loader2 className="text-muted-foreground size-16 animate-spin" />}
|
||||
title="Loading tokens..."
|
||||
/>
|
||||
<Empty>
|
||||
<EmptyHeader>
|
||||
<EmptyMedia>
|
||||
<Loader2 className="text-muted-foreground size-10 animate-spin" />
|
||||
</EmptyMedia>
|
||||
<EmptyTitle>Loading tokens...</EmptyTitle>
|
||||
<EmptyDescription>Please wait while we fetch your API tokens</EmptyDescription>
|
||||
</EmptyHeader>
|
||||
</Empty>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
@@ -872,7 +881,6 @@ function SettingsAPITokens() {
|
||||
<>
|
||||
{pageHeader}
|
||||
<div className="flex flex-1 flex-col gap-4 p-4">
|
||||
<SettingsAPITokensHeader onCreateClick={handleCreateNew} />
|
||||
<Alert variant="destructive">
|
||||
<AlertCircle className="size-4" />
|
||||
<AlertTitle>Error loading tokens</AlertTitle>
|
||||
@@ -890,9 +898,17 @@ function SettingsAPITokens() {
|
||||
<>
|
||||
{pageHeader}
|
||||
<div className="flex flex-1 flex-col gap-4 p-4">
|
||||
<SettingsAPITokensHeader onCreateClick={handleCreateNew} />
|
||||
<StatusCard
|
||||
action={
|
||||
<Empty>
|
||||
<EmptyHeader>
|
||||
<EmptyMedia variant="icon">
|
||||
<Key />
|
||||
</EmptyMedia>
|
||||
<EmptyTitle>No API tokens configured</EmptyTitle>
|
||||
<EmptyDescription>
|
||||
Create your first API token to access PentAGI programmatically
|
||||
</EmptyDescription>
|
||||
</EmptyHeader>
|
||||
<EmptyContent>
|
||||
<Button
|
||||
onClick={handleCreateNew}
|
||||
variant="secondary"
|
||||
@@ -900,11 +916,8 @@ function SettingsAPITokens() {
|
||||
<Plus className="size-4" />
|
||||
Create Token
|
||||
</Button>
|
||||
}
|
||||
description="Create your first API token to access PentAGI programmatically"
|
||||
icon={<Key className="text-muted-foreground size-8" />}
|
||||
title="No API tokens configured"
|
||||
/>
|
||||
</EmptyContent>
|
||||
</Empty>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
@@ -914,8 +927,6 @@ function SettingsAPITokens() {
|
||||
<>
|
||||
{pageHeader}
|
||||
<div className="flex flex-1 flex-col gap-4 p-4">
|
||||
<SettingsAPITokensHeader onCreateClick={handleCreateNew} />
|
||||
|
||||
{(createError || updateError || deleteError || deleteErrorMessage) && (
|
||||
<Alert variant="destructive">
|
||||
<AlertCircle className="size-4" />
|
||||
|
||||
@@ -68,9 +68,9 @@ import {
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from '@/components/ui/dropdown-menu';
|
||||
import { Empty, EmptyDescription, EmptyHeader, EmptyMedia, EmptyTitle } from '@/components/ui/empty';
|
||||
import { Form, FormControl, FormItem, FormMessage } from '@/components/ui/form';
|
||||
import { FormSubmitButton } from '@/components/ui/form-submit-button';
|
||||
import { StatusCard } from '@/components/ui/status-card';
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
||||
import {
|
||||
CreatePromptDocument,
|
||||
@@ -733,11 +733,15 @@ function SettingsPrompt() {
|
||||
<>
|
||||
{pageHeader}
|
||||
<div className="flex flex-1 items-center justify-center p-4">
|
||||
<StatusCard
|
||||
description="Please wait while we fetch prompt information"
|
||||
icon={<Loader2 className="text-muted-foreground size-16 animate-spin" />}
|
||||
title="Loading prompt data..."
|
||||
/>
|
||||
<Empty>
|
||||
<EmptyHeader>
|
||||
<EmptyMedia>
|
||||
<Loader2 className="text-muted-foreground size-10 animate-spin" />
|
||||
</EmptyMedia>
|
||||
<EmptyTitle>Loading prompt data...</EmptyTitle>
|
||||
<EmptyDescription>Please wait while we fetch prompt information</EmptyDescription>
|
||||
</EmptyHeader>
|
||||
</Empty>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
@@ -748,11 +752,15 @@ function SettingsPrompt() {
|
||||
<>
|
||||
{pageHeader}
|
||||
<div className="flex flex-1 items-center justify-center p-4">
|
||||
<StatusCard
|
||||
description={error.message}
|
||||
icon={<AlertCircle className="text-destructive size-16" />}
|
||||
title="Error loading prompt data"
|
||||
/>
|
||||
<Empty>
|
||||
<EmptyHeader>
|
||||
<EmptyMedia>
|
||||
<AlertCircle className="text-destructive size-12" />
|
||||
</EmptyMedia>
|
||||
<EmptyTitle>Error loading prompt data</EmptyTitle>
|
||||
<EmptyDescription>{error.message}</EmptyDescription>
|
||||
</EmptyHeader>
|
||||
</Empty>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
@@ -763,11 +771,15 @@ function SettingsPrompt() {
|
||||
<>
|
||||
{pageHeader}
|
||||
<div className="flex flex-1 items-center justify-center p-4">
|
||||
<StatusCard
|
||||
description={`The prompt "${promptId}" could not be found or is not supported for editing.`}
|
||||
icon={<AlertCircle className="text-destructive size-16" />}
|
||||
title="Prompt not found"
|
||||
/>
|
||||
<Empty>
|
||||
<EmptyHeader>
|
||||
<EmptyMedia>
|
||||
<AlertCircle className="text-destructive size-12" />
|
||||
</EmptyMedia>
|
||||
<EmptyTitle>Prompt not found</EmptyTitle>
|
||||
<EmptyDescription>{`The prompt "${promptId}" could not be found or is not supported for editing.`}</EmptyDescription>
|
||||
</EmptyHeader>
|
||||
</Empty>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -39,7 +39,7 @@ import {
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from '@/components/ui/dropdown-menu';
|
||||
import { StatusCard } from '@/components/ui/status-card';
|
||||
import { Empty, EmptyDescription, EmptyHeader, EmptyMedia, EmptyTitle } from '@/components/ui/empty';
|
||||
import { DeletePromptDocument, SettingsPromptsDocument } from '@/graphql/types';
|
||||
import { usePageStorageKeys } from '@/hooks/use-page-storage-keys';
|
||||
import { routes } from '@/lib/routes';
|
||||
@@ -323,8 +323,8 @@ function SettingsPrompts() {
|
||||
{
|
||||
accessorKey: 'displayName',
|
||||
cell: ({ row }) => (
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="font-medium">{row.original.displayName}</span>
|
||||
<div className="flex min-w-0 items-center gap-2">
|
||||
<span className="truncate font-medium">{row.original.displayName}</span>
|
||||
</div>
|
||||
),
|
||||
enableHiding: false,
|
||||
@@ -347,7 +347,6 @@ function SettingsPrompts() {
|
||||
);
|
||||
},
|
||||
meta: { columnMenuLabel: 'Agent Name', searchable: true },
|
||||
size: 200,
|
||||
},
|
||||
{
|
||||
accessorKey: 'systemStatus',
|
||||
@@ -495,8 +494,8 @@ function SettingsPrompts() {
|
||||
{
|
||||
accessorKey: 'displayName',
|
||||
cell: ({ row }) => (
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="font-medium">{row.original.displayName}</span>
|
||||
<div className="flex min-w-0 items-center gap-2">
|
||||
<span className="truncate font-medium">{row.original.displayName}</span>
|
||||
</div>
|
||||
),
|
||||
enableHiding: false,
|
||||
@@ -519,7 +518,6 @@ function SettingsPrompts() {
|
||||
);
|
||||
},
|
||||
meta: { columnMenuLabel: 'Tool Name', searchable: true },
|
||||
size: 300,
|
||||
},
|
||||
{
|
||||
accessorKey: 'status',
|
||||
@@ -793,11 +791,15 @@ function SettingsPrompts() {
|
||||
{pageHeader}
|
||||
<div className="flex flex-1 flex-col gap-6 p-4">
|
||||
<SettingsPromptsHeader />
|
||||
<StatusCard
|
||||
description="Please wait while we fetch your prompt templates"
|
||||
icon={<Loader2 className="text-muted-foreground size-16 animate-spin" />}
|
||||
title="Loading prompts..."
|
||||
/>
|
||||
<Empty>
|
||||
<EmptyHeader>
|
||||
<EmptyMedia>
|
||||
<Loader2 className="text-muted-foreground size-10 animate-spin" />
|
||||
</EmptyMedia>
|
||||
<EmptyTitle>Loading prompts...</EmptyTitle>
|
||||
<EmptyDescription>Please wait while we fetch your prompt templates</EmptyDescription>
|
||||
</EmptyHeader>
|
||||
</Empty>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
@@ -828,11 +830,15 @@ function SettingsPrompts() {
|
||||
{pageHeader}
|
||||
<div className="flex flex-1 flex-col gap-6 p-4">
|
||||
<SettingsPromptsHeader />
|
||||
<StatusCard
|
||||
description="Prompt templates could not be loaded"
|
||||
icon={<Settings className="text-muted-foreground size-8" />}
|
||||
title="No prompts available"
|
||||
/>
|
||||
<Empty>
|
||||
<EmptyHeader>
|
||||
<EmptyMedia variant="icon">
|
||||
<Settings />
|
||||
</EmptyMedia>
|
||||
<EmptyTitle>No prompts available</EmptyTitle>
|
||||
<EmptyDescription>Prompt templates could not be loaded</EmptyDescription>
|
||||
</EmptyHeader>
|
||||
</Empty>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -51,13 +51,13 @@ import {
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from '@/components/ui/dropdown-menu';
|
||||
import { Empty, EmptyDescription, EmptyHeader, EmptyMedia, EmptyTitle } from '@/components/ui/empty';
|
||||
import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from '@/components/ui/form';
|
||||
import { FormSubmitButton } from '@/components/ui/form-submit-button';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { InputGroup, InputGroupAddon, InputGroupButton, InputGroupInput } from '@/components/ui/input-group';
|
||||
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
|
||||
import { StatusCard } from '@/components/ui/status-card';
|
||||
import {
|
||||
AgentConfigType,
|
||||
CreateProviderDocument,
|
||||
@@ -1558,11 +1558,15 @@ function SettingsProvider() {
|
||||
</AppHeaderContent>
|
||||
</AppHeader>
|
||||
<div className="flex flex-1 items-center justify-center p-4">
|
||||
<StatusCard
|
||||
description="Please wait while we fetch provider configuration"
|
||||
icon={<Loader2 className="text-muted-foreground size-16 animate-spin" />}
|
||||
title="Loading provider data..."
|
||||
/>
|
||||
<Empty>
|
||||
<EmptyHeader>
|
||||
<EmptyMedia>
|
||||
<Loader2 className="text-muted-foreground size-10 animate-spin" />
|
||||
</EmptyMedia>
|
||||
<EmptyTitle>Loading provider data...</EmptyTitle>
|
||||
<EmptyDescription>Please wait while we fetch provider configuration</EmptyDescription>
|
||||
</EmptyHeader>
|
||||
</Empty>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
@@ -1579,11 +1583,15 @@ function SettingsProvider() {
|
||||
</AppHeaderContent>
|
||||
</AppHeader>
|
||||
<div className="flex flex-1 items-center justify-center p-4">
|
||||
<StatusCard
|
||||
description={error.message}
|
||||
icon={<AlertCircle className="text-destructive size-16" />}
|
||||
title="Error loading provider data"
|
||||
/>
|
||||
<Empty>
|
||||
<EmptyHeader>
|
||||
<EmptyMedia>
|
||||
<AlertCircle className="text-destructive size-12" />
|
||||
</EmptyMedia>
|
||||
<EmptyTitle>Error loading provider data</EmptyTitle>
|
||||
<EmptyDescription>{error.message}</EmptyDescription>
|
||||
</EmptyHeader>
|
||||
</Empty>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -8,7 +8,7 @@ import { useNavigate } from 'react-router-dom';
|
||||
import type { ProviderConfigFragmentFragment } from '@/graphql/types';
|
||||
|
||||
import { providerIcons } from '@/components/icons/provider-icon';
|
||||
import { AppHeader, AppHeaderContent, AppHeaderTitle } from '@/components/layouts/app/app-header';
|
||||
import { AppHeader, AppHeaderActions, AppHeaderContent, AppHeaderTitle } from '@/components/layouts/app/app-header';
|
||||
import ConfirmationDialog from '@/components/shared/confirmation-dialog';
|
||||
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
@@ -22,7 +22,7 @@ import {
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from '@/components/ui/dropdown-menu';
|
||||
import { StatusCard } from '@/components/ui/status-card';
|
||||
import { Empty, EmptyContent, EmptyDescription, EmptyHeader, EmptyMedia, EmptyTitle } from '@/components/ui/empty';
|
||||
import { DeleteProviderDocument, ProviderType, SettingsProvidersDocument } from '@/graphql/types';
|
||||
import { useTableState } from '@/hooks/use-table-state';
|
||||
import { routes } from '@/lib/routes';
|
||||
@@ -65,46 +65,39 @@ export function SettingsProvidersHeader() {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex items-center justify-between gap-4">
|
||||
<p className="text-muted-foreground min-w-0 flex-1 truncate">Manage language model providers</p>
|
||||
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
aria-label="Create provider — choose type"
|
||||
className="shrink-0"
|
||||
variant="secondary"
|
||||
>
|
||||
Create Provider
|
||||
<ChevronDown className="size-4" />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent
|
||||
align="end"
|
||||
style={{
|
||||
width: 'var(--radix-dropdown-menu-trigger-width)',
|
||||
}}
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
aria-label="Create provider — choose type"
|
||||
className="w-8 shrink-0 px-0 md:w-auto md:px-3"
|
||||
size="sm"
|
||||
variant="secondary"
|
||||
>
|
||||
{availableTypes.length === 0 ? (
|
||||
<DropdownMenuItem disabled>No available provider types</DropdownMenuItem>
|
||||
) : (
|
||||
availableTypes.map(({ label, type }) => {
|
||||
const Icon = providerIcons[type]?.icon;
|
||||
<Plus />
|
||||
<span className="hidden md:inline">Create Provider</span>
|
||||
<ChevronDown className="hidden size-4 md:inline-flex" />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
{availableTypes.length === 0 ? (
|
||||
<DropdownMenuItem disabled>No available provider types</DropdownMenuItem>
|
||||
) : (
|
||||
availableTypes.map(({ label, type }) => {
|
||||
const Icon = providerIcons[type]?.icon;
|
||||
|
||||
return (
|
||||
<DropdownMenuItem
|
||||
key={type}
|
||||
onClick={() => handleProviderCreate(type)}
|
||||
>
|
||||
{Icon && <Icon className="size-4" />}
|
||||
{label}
|
||||
</DropdownMenuItem>
|
||||
);
|
||||
})
|
||||
)}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
return (
|
||||
<DropdownMenuItem
|
||||
key={type}
|
||||
onClick={() => handleProviderCreate(type)}
|
||||
>
|
||||
{Icon && <Icon className="size-4" />}
|
||||
{label}
|
||||
</DropdownMenuItem>
|
||||
);
|
||||
})
|
||||
)}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -404,6 +397,9 @@ function SettingsProviders() {
|
||||
<AppHeaderContent>
|
||||
<AppHeaderTitle icon={<Plug className="size-4 shrink-0" />}>Providers</AppHeaderTitle>
|
||||
</AppHeaderContent>
|
||||
<AppHeaderActions>
|
||||
<SettingsProvidersHeader />
|
||||
</AppHeaderActions>
|
||||
</AppHeader>
|
||||
);
|
||||
|
||||
@@ -412,12 +408,17 @@ function SettingsProviders() {
|
||||
<>
|
||||
{pageHeader}
|
||||
<div className="flex flex-1 flex-col gap-4 p-4">
|
||||
<SettingsProvidersHeader />
|
||||
<StatusCard
|
||||
description="Please wait while we fetch your provider configurations"
|
||||
icon={<Loader2 className="text-muted-foreground size-16 animate-spin" />}
|
||||
title="Loading providers..."
|
||||
/>
|
||||
<Empty>
|
||||
<EmptyHeader>
|
||||
<EmptyMedia>
|
||||
<Loader2 className="text-muted-foreground size-10 animate-spin" />
|
||||
</EmptyMedia>
|
||||
<EmptyTitle>Loading providers...</EmptyTitle>
|
||||
<EmptyDescription>
|
||||
Please wait while we fetch your provider configurations
|
||||
</EmptyDescription>
|
||||
</EmptyHeader>
|
||||
</Empty>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
@@ -428,7 +429,6 @@ function SettingsProviders() {
|
||||
<>
|
||||
{pageHeader}
|
||||
<div className="flex flex-1 flex-col gap-4 p-4">
|
||||
<SettingsProvidersHeader />
|
||||
<Alert variant="destructive">
|
||||
<AlertCircle className="size-4" />
|
||||
<AlertTitle>Error loading providers</AlertTitle>
|
||||
@@ -446,9 +446,17 @@ function SettingsProviders() {
|
||||
<>
|
||||
{pageHeader}
|
||||
<div className="flex flex-1 flex-col gap-4 p-4">
|
||||
<SettingsProvidersHeader />
|
||||
<StatusCard
|
||||
action={
|
||||
<Empty>
|
||||
<EmptyHeader>
|
||||
<EmptyMedia variant="icon">
|
||||
<Settings />
|
||||
</EmptyMedia>
|
||||
<EmptyTitle>No providers configured</EmptyTitle>
|
||||
<EmptyDescription>
|
||||
Get started by adding your first language model provider
|
||||
</EmptyDescription>
|
||||
</EmptyHeader>
|
||||
<EmptyContent>
|
||||
<Button
|
||||
onClick={() => navigate(routes.settings.newProvider())}
|
||||
variant="secondary"
|
||||
@@ -456,11 +464,8 @@ function SettingsProviders() {
|
||||
<Plus className="size-4" />
|
||||
Add Provider
|
||||
</Button>
|
||||
}
|
||||
description="Get started by adding your first language model provider"
|
||||
icon={<Settings className="text-muted-foreground size-8" />}
|
||||
title="No providers configured"
|
||||
/>
|
||||
</EmptyContent>
|
||||
</Empty>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
@@ -470,8 +475,6 @@ function SettingsProviders() {
|
||||
<>
|
||||
{pageHeader}
|
||||
<div className="flex flex-1 flex-col gap-4 p-4">
|
||||
<SettingsProvidersHeader />
|
||||
|
||||
{(deleteError || deleteErrorMessage) && (
|
||||
<Alert variant="destructive">
|
||||
<AlertCircle className="size-4" />
|
||||
|
||||
@@ -24,7 +24,7 @@ import {
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from '@/components/ui/dropdown-menu';
|
||||
import { StatusCard } from '@/components/ui/status-card';
|
||||
import { Empty, EmptyContent, EmptyDescription, EmptyHeader, EmptyMedia, EmptyTitle } from '@/components/ui/empty';
|
||||
import { useTableState } from '@/hooks/use-table-state';
|
||||
import { routes } from '@/lib/routes';
|
||||
import { mergeHrefWithSearchParams } from '@/lib/url-params';
|
||||
@@ -268,8 +268,15 @@ function Templates() {
|
||||
<>
|
||||
{pageHeader}
|
||||
<div className="flex flex-col gap-4 p-4">
|
||||
<StatusCard
|
||||
action={
|
||||
<Empty>
|
||||
<EmptyHeader>
|
||||
<EmptyMedia variant="icon">
|
||||
<FileText />
|
||||
</EmptyMedia>
|
||||
<EmptyTitle>No templates yet</EmptyTitle>
|
||||
<EmptyDescription>Create your first template to get started</EmptyDescription>
|
||||
</EmptyHeader>
|
||||
<EmptyContent>
|
||||
<Button
|
||||
onClick={() => navigate(routes.newTemplate)}
|
||||
variant="secondary"
|
||||
@@ -277,11 +284,8 @@ function Templates() {
|
||||
<Plus className="size-4" />
|
||||
New Template
|
||||
</Button>
|
||||
}
|
||||
description="Create your first template to get started"
|
||||
icon={<FileText className="text-muted-foreground size-8" />}
|
||||
title="No templates yet"
|
||||
/>
|
||||
</EmptyContent>
|
||||
</Empty>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user