)}
@@ -218,7 +238,7 @@ const Flow = () => {
minSize={30}
>
-
diff --git a/frontend/src/pages/flows/flows.tsx b/frontend/src/pages/flows/flows.tsx
index 4045f11e..d4efc2bb 100644
--- a/frontend/src/pages/flows/flows.tsx
+++ b/frontend/src/pages/flows/flows.tsx
@@ -5,10 +5,7 @@ import { enUS } from 'date-fns/locale';
import {
ArrowDown,
ArrowUp,
- Check,
- CheckCircle2,
Eye,
- FileText,
GitFork,
Loader2,
MoreHorizontal,
@@ -17,11 +14,12 @@ import {
Plus,
Star,
Trash,
- X,
- XCircle,
} from 'lucide-react';
-import { useCallback, useMemo, useState } from 'react';
-import { useNavigate, useSearchParams } from 'react-router-dom';
+import { Check, CheckCircle2, X, XCircle } from 'lucide-react';
+import { useState } from 'react';
+import { useCallback, useMemo } from 'react';
+import { useNavigate } from 'react-router-dom';
+import { useSearchParams } from 'react-router-dom';
import { toast } from 'sonner';
import { FlowStatusIcon } from '@/components/icons/flow-status-icon';
@@ -30,6 +28,7 @@ import ConfirmationDialog from '@/components/shared/confirmation-dialog';
import { Badge } from '@/components/ui/badge';
import { Breadcrumb, BreadcrumbItem, BreadcrumbList, BreadcrumbPage } from '@/components/ui/breadcrumb';
import { Button } from '@/components/ui/button';
+import { ContextMenuItem, ContextMenuSeparator } from '@/components/ui/context-menu';
import { DataTable } from '@/components/ui/data-table';
import {
DropdownMenu,
@@ -38,14 +37,13 @@ import {
DropdownMenuSeparator,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';
-import { Input } from '@/components/ui/input';
+import { InputGroup, InputGroupAddon, InputGroupButton, InputGroupInput } from '@/components/ui/input-group';
import { Separator } from '@/components/ui/separator';
import { SidebarTrigger } from '@/components/ui/sidebar';
import { StatusCard } from '@/components/ui/status-card';
import { Toggle } from '@/components/ui/toggle';
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
import { ResultType, StatusType, type TerminalFragmentFragment, useRenameFlowMutation } from '@/graphql/types';
-import { useAdaptiveColumnVisibility } from '@/hooks/use-adaptive-column-visibility';
import { useFavorites } from '@/providers/favorites-provider';
import { type Flow, useFlows } from '@/providers/flows-provider';
@@ -104,19 +102,6 @@ const Flows = () => {
const [editingFlowTitle, setEditingFlowTitle] = useState('');
const [renameFlowMutation, { loading: isRenameLoading }] = useRenameFlowMutation();
- const { columnVisibility, updateColumnVisibility } = useAdaptiveColumnVisibility({
- columns: [
- { alwaysVisible: true, id: 'id', priority: 0 },
- { alwaysVisible: true, id: 'title', priority: 0 },
- { id: 'status', priority: 1 },
- { id: 'provider', priority: 2 },
- { id: 'createdAt', priority: 3 },
- { id: 'updatedAt', priority: 4 },
- { id: 'terminals', priority: 5 },
- ],
- tableKey: 'flows',
- });
-
// Three-way sorting handler: null -> asc -> desc -> null
const handleColumnSort = useMemo(
() =>
@@ -285,21 +270,44 @@ const Flows = () => {
if (isEditing) {
return (
-
setEditingFlowTitle(e.target.value)}
onClick={(e) => e.stopPropagation()}
- onKeyDown={(e) => {
- if (e.key === 'Enter') {
- handleFlowRenameSave();
- } else if (e.key === 'Escape') {
- handleFlowRenameCancel();
- }
- }}
- placeholder="Flow title"
- value={editingFlowTitle}
- />
+ >
+
setEditingFlowTitle(e.target.value)}
+ onKeyDown={(e) => {
+ if (e.key === 'Enter') {
+ handleFlowRenameSave();
+
+ return;
+ }
+
+ if (e.key === 'Escape') {
+ handleFlowRenameCancel();
+
+ return;
+ }
+ }}
+ placeholder="Flow title"
+ value={editingFlowTitle}
+ />
+
+ handleFlowRenameSave()}
+ >
+ {isRenameLoading ? : }
+
+ handleFlowRenameCancel()}>
+
+
+
+
);
}
@@ -571,39 +579,6 @@ const Flows = () => {
cell: ({ row }) => {
const flow = row.original;
const isRunning = ![StatusType.Failed, StatusType.Finished].includes(flow.status);
- const isEditing = editingFlowId === flow.id;
-
- if (isEditing) {
- return (
-
-
-
-
- );
- }
return (
@@ -687,6 +662,7 @@ const Flows = () => {
header: () => null,
id: 'actions',
maxSize: 100,
+ meta: { preventRowClick: true },
minSize: 90,
size: 96,
},
@@ -709,7 +685,58 @@ const Flows = () => {
],
);
- // Memoize onRowClick to prevent unnecessary rerenders
+ const renderRowContextMenu = useCallback(
+ (flow: Flow) => {
+ const isRunning = ![StatusType.Failed, StatusType.Finished].includes(flow.status);
+
+ return (
+ <>
+
toggleFavoriteFlow(flow.id)}>
+
+ {isFavoriteFlow(flow.id) ? 'Remove from favorites' : 'Add to favorites'}
+
+
+
handleFlowOpen(flow.id)}>
+
+ View
+
+
handleFlowRenameStart(flow)}>
+
+ Rename
+
+
+ {isRunning && (
+
handleFlowFinish(flow)}
+ >
+
+ {finishingFlowIds.has(flow.id) ? 'Finishing...' : 'Finish'}
+
+ )}
+
+
handleFlowDeleteDialogOpen(flow)}
+ >
+
+ {deletingFlowIds.has(flow.id) ? 'Deleting...' : 'Delete'}
+
+ >
+ );
+ },
+ [
+ deletingFlowIds,
+ finishingFlowIds,
+ handleFlowDeleteDialogOpen,
+ handleFlowFinish,
+ handleFlowOpen,
+ handleFlowRenameStart,
+ isFavoriteFlow,
+ toggleFavoriteFlow,
+ ],
+ );
+
const handleRowClick = useCallback(
(flow: Flow) => {
if (editingFlowId !== flow.id) {
@@ -776,12 +803,12 @@ const Flows = () => {
onClick={() => navigate('/flows/new')}
variant="secondary"
>
-
+
New Flow
}
description="Get started by creating your first conversation flow"
- icon={
}
+ icon={
}
title="No flows found"
/>
@@ -795,21 +822,13 @@ const Flows = () => {
columns={columns}
- columnVisibility={columnVisibility}
data={flows}
filterColumn="title"
filterPlaceholder="Filter flows..."
- onColumnVisibilityChange={(visibility) => {
- Object.entries(visibility).forEach(([columnId, isVisible]) => {
- if (columnVisibility[columnId] !== isVisible) {
- updateColumnVisibility(columnId, isVisible);
- }
- });
- }}
onPageChange={handlePageChange}
onRowClick={handleRowClick}
pageIndex={currentPage}
- tableKey="flows"
+ renderRowContextMenu={renderRowContextMenu}
/>
{
return (
<>
-
+
{
Create a new flow
-
Describe what you would like PentAGI to test
+
Describe what you would like PentAGI to test
setFlowType(value as 'assistant' | 'automation')}
diff --git a/frontend/src/pages/oauth-result.tsx b/frontend/src/pages/oauth-result.tsx
index ee0e7466..4b87514d 100644
--- a/frontend/src/pages/oauth-result.tsx
+++ b/frontend/src/pages/oauth-result.tsx
@@ -110,7 +110,7 @@ const OAuthResult = () => {
return (
);
diff --git a/frontend/src/pages/settings/settings-api-tokens.tsx b/frontend/src/pages/settings/settings-api-tokens.tsx
index f66ccd04..d4cbaa06 100644
--- a/frontend/src/pages/settings/settings-api-tokens.tsx
+++ b/frontend/src/pages/settings/settings-api-tokens.tsx
@@ -29,6 +29,7 @@ import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert';
import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button';
import { Calendar } from '@/components/ui/calendar';
+import { ContextMenuItem, ContextMenuSeparator } from '@/components/ui/context-menu';
import { DataTable } from '@/components/ui/data-table';
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from '@/components/ui/dialog';
import {
@@ -53,7 +54,6 @@ import {
useDeleteApiTokenMutation,
useUpdateApiTokenMutation,
} from '@/graphql/types';
-import { useAdaptiveColumnVisibility } from '@/hooks/use-adaptive-column-visibility';
import { cn } from '@/lib/utils';
import { baseUrl } from '@/models/api';
@@ -208,16 +208,6 @@ const SettingsAPITokens = () => {
const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false);
const [deletingToken, setDeletingToken] = useState(null);
- const { columnVisibility, updateColumnVisibility } = useAdaptiveColumnVisibility({
- columns: [
- { alwaysVisible: true, id: 'name', priority: 0 },
- { alwaysVisible: true, id: 'tokenId', priority: 0 },
- { id: 'status', priority: 1 },
- { id: 'createdAt', priority: 2 },
- { id: 'expires', priority: 3 },
- ],
- tableKey: 'api-tokens',
- });
// Get current page from URL
const currentPage = useMemo(() => {
@@ -803,6 +793,7 @@ const SettingsAPITokens = () => {
enableHiding: false,
header: () => null,
id: 'actions',
+ meta: { preventRowClick: true },
size: 48,
},
],
@@ -827,6 +818,36 @@ const SettingsAPITokens = () => {
],
);
+ const renderRowContextMenu = useCallback(
+ (token: APIToken) => {
+ if (token.id === 'create-new') {
+ return null;
+ }
+
+ return (
+ <>
+ handleEdit(token)}>
+
+ Edit
+
+ handleCopyTokenId(token.tokenId)}>
+
+ Copy Token ID
+
+
+ handleDeleteDialogOpen(token)}
+ >
+
+ {isDeleteLoading && deletingToken?.tokenId === token.tokenId ? 'Deleting...' : 'Delete'}
+
+ >
+ );
+ },
+ [deletingToken, handleCopyTokenId, handleDeleteDialogOpen, handleEdit, isDeleteLoading],
+ );
+
if (isLoading) {
return (
@@ -893,20 +914,12 @@ const SettingsAPITokens = () => {
columns={columns}
- columnVisibility={columnVisibility}
data={creatingToken ? [createNewTokenPlaceholder, ...tokens] : tokens}
filterColumn="name"
filterPlaceholder="Filter token names..."
- onColumnVisibilityChange={(visibility) => {
- Object.entries(visibility).forEach(([columnId, isVisible]) => {
- if (columnVisibility[columnId] !== isVisible) {
- updateColumnVisibility(columnId, isVisible);
- }
- });
- }}
onPageChange={handlePageChange}
pageIndex={currentPage}
- tableKey="api-tokens"
+ renderRowContextMenu={renderRowContextMenu}
/>