Files
DocsGPT/frontend/src/components/ui/multi-select.tsx
Siddhant Rai 8ef321d784 feat: agent workflow builder (#2264)
* feat: implement WorkflowAgent and GraphExecutor for workflow management and execution

* refactor: workflow schemas and introduce WorkflowEngine

- Updated schemas in `schemas.py` to include new agent types and configurations.
- Created `WorkflowEngine` class in `workflow_engine.py` to manage workflow execution.
- Enhanced `StreamProcessor` to handle workflow-related data.
- Added new routes and utilities for managing workflows in the user API.
- Implemented validation and serialization functions for workflows.
- Established MongoDB collections and indexes for workflows and related entities.

* refactor: improve WorkflowAgent documentation and update type hints in WorkflowEngine

* feat: workflow builder and managing in frontend

- Added new endpoints for workflows in `endpoints.ts`.
- Implemented `getWorkflow`, `createWorkflow`, and `updateWorkflow` methods in `userService.ts`.
- Introduced new UI components for alerts, buttons, commands, dialogs, multi-select, popovers, and selects.
- Enhanced styling in `index.css` with new theme variables and animations.
- Refactored modal components for better layout and styling.
- Configured TypeScript paths and Vite aliases for cleaner imports.

* feat: add workflow preview component and related state management

- Implemented WorkflowPreview component for displaying workflow execution.
- Created WorkflowPreviewSlice for managing workflow preview state, including queries and execution steps.
- Added WorkflowMiniMap for visual representation of workflow nodes and their statuses.
- Integrated conversation handling with the ability to fetch answers and manage query states.
- Introduced reusable Sheet component for UI overlays.
- Updated Redux store to include workflowPreview reducer.

* feat: enhance workflow execution details and state management in WorkflowEngine and WorkflowPreview

* feat: enhance workflow components with improved UI and functionality

- Updated WorkflowPreview to allow text truncation for better display of long names.
- Enhanced BaseNode with connectable handles and improved styling for better visibility.
- Added MobileBlocker component to inform users about desktop requirements for the Workflow Builder.
- Introduced PromptTextArea component for improved variable insertion and search functionality, including upstream variable extraction and context addition.

* feat(workflow): add owner validation and graph version support

* fix: ruff lint

---------

Co-authored-by: Alex <a@tushynski.me>
2026-02-11 14:15:24 +00:00

167 lines
5.5 KiB
TypeScript

'use client';
import { Check, ChevronsUpDown, X } from 'lucide-react';
import * as React from 'react';
import { Button } from '@/components/ui/button';
import {
Command,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
} from '@/components/ui/command';
import {
Popover,
PopoverContent,
PopoverTrigger,
} from '@/components/ui/popover';
import { cn } from '@/lib/utils';
export interface MultiSelectOption {
value: string;
label: string;
}
interface MultiSelectProps {
options: MultiSelectOption[];
selected: string[];
onChange: (selected: string[]) => void;
placeholder?: string;
emptyText?: string;
searchPlaceholder?: string;
className?: string;
}
export function MultiSelect({
options,
selected,
onChange,
placeholder = 'Select items...',
emptyText = 'No results found.',
searchPlaceholder = 'Search...',
className,
}: MultiSelectProps) {
const [open, setOpen] = React.useState(false);
const handleSelect = (value: string) => {
const newSelected = selected.includes(value)
? selected.filter((item) => item !== value)
: [...selected, value];
onChange(newSelected);
};
const handleRemove = (value: string, e: React.MouseEvent) => {
e.preventDefault();
e.stopPropagation();
onChange(selected.filter((item) => item !== value));
};
const selectedLabels = options
.filter((option) => selected.includes(option.value))
.map((option) => option.label);
return (
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger asChild>
<Button
variant="outline"
role="combobox"
aria-expanded={open}
className={cn(
'w-full justify-between border-[#E5E5E5] bg-white hover:bg-gray-50 dark:border-[#3A3A3A] dark:bg-[#2C2C2C] dark:hover:bg-[#383838]',
!selected.length && 'text-gray-500 dark:text-gray-400',
className,
)}
>
<div className="flex flex-wrap gap-1">
{selected.length === 0 ? (
placeholder
) : (
<>
{selectedLabels.slice(0, 2).map((label) => {
const option = options.find((o) => o.label === label);
return (
<span
key={option?.value || label}
className="dark:bg-purple-30/30 bg-violets-are-blue/20 inline-flex items-center gap-1 rounded-md px-2 py-0.5 text-xs font-medium text-purple-700 dark:text-purple-300"
>
{label}
<span
role="button"
tabIndex={0}
className="flex h-3 w-3 cursor-pointer items-center justify-center hover:text-purple-900 dark:hover:text-purple-200"
onMouseDown={(e) => {
e.preventDefault();
e.stopPropagation();
}}
onClick={(e) => handleRemove(option?.value || '', e)}
onKeyDown={(e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
handleRemove(
option?.value || '',
e as unknown as React.MouseEvent,
);
}
}}
>
<X className="h-3 w-3" />
</span>
</span>
);
})}
{selected.length > 2 && (
<span className="text-xs text-gray-600 dark:text-gray-400">
+{selected.length - 2} more
</span>
)}
</>
)}
</div>
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
</Button>
</PopoverTrigger>
<PopoverContent
className="w-(--radix-popover-trigger-width) border-[#E5E5E5] bg-white p-0 dark:border-[#3A3A3A] dark:bg-[#2C2C2C]"
align="start"
>
<Command className="bg-transparent">
<CommandInput placeholder={searchPlaceholder} className="h-9" />
<CommandList>
<CommandEmpty className="py-2 text-center text-sm">
{emptyText}
</CommandEmpty>
<CommandGroup className="p-1">
{options.map((option) => {
const isSelected = selected.includes(option.value);
return (
<CommandItem
key={option.value}
value={option.label}
onSelect={() => handleSelect(option.value)}
className="cursor-pointer dark:hover:bg-[#383838]"
>
<div
className={cn(
'mr-2 flex h-4 w-4 items-center justify-center rounded-sm border-2',
isSelected
? 'border-purple-30 bg-purple-30 text-white'
: 'border-gray-400 dark:border-gray-500',
)}
>
{isSelected && <Check className="h-3 w-3 stroke-white" />}
</div>
{option.label}
</CommandItem>
);
})}
</CommandGroup>
</CommandList>
</Command>
</PopoverContent>
</Popover>
);
}