- Updated the widget processing API to accept tool names as an optional parameter. - Consolidated tool imports and created an `allTools` array for easier management. - Added a new ToolSelector component for selecting tools in the widget configuration modal. - Enhanced date difference and timezone conversion tools with improved descriptions and error handling. - Refactored types for widgets and dashboard to streamline the codebase and improve type safety. - Removed deprecated types and organized type definitions into separate files for better maintainability.
166 lines
5.9 KiB
TypeScript
166 lines
5.9 KiB
TypeScript
import {
|
|
Wrench,
|
|
ChevronDown,
|
|
CheckSquare,
|
|
Square,
|
|
Loader2,
|
|
} from 'lucide-react';
|
|
import { cn } from '@/lib/utils';
|
|
import {
|
|
Popover,
|
|
PopoverButton,
|
|
PopoverPanel,
|
|
Transition,
|
|
} from '@headlessui/react';
|
|
import { Fragment, useEffect, useState } from 'react';
|
|
|
|
interface Tool {
|
|
name: string;
|
|
description: string;
|
|
}
|
|
|
|
interface ToolSelectorProps {
|
|
selectedToolNames: string[];
|
|
onSelectedToolNamesChange: (names: string[]) => void;
|
|
}
|
|
|
|
const ToolSelector = ({
|
|
selectedToolNames,
|
|
onSelectedToolNamesChange,
|
|
}: ToolSelectorProps) => {
|
|
const [availableTools, setAvailableTools] = useState<Tool[]>([]);
|
|
const [isLoading, setIsLoading] = useState(true);
|
|
|
|
useEffect(() => {
|
|
const fetchTools = async () => {
|
|
try {
|
|
setIsLoading(true);
|
|
const response = await fetch('/api/tools');
|
|
if (response.ok) {
|
|
const tools = await response.json();
|
|
setAvailableTools(tools);
|
|
|
|
// Check if any currently selected tool names are not in the API response
|
|
const availableToolNames = tools.map((tool: Tool) => tool.name);
|
|
const validSelectedNames = selectedToolNames.filter((name) =>
|
|
availableToolNames.includes(name),
|
|
);
|
|
|
|
// If some selected names are no longer available, update the selection
|
|
if (validSelectedNames.length !== selectedToolNames.length) {
|
|
onSelectedToolNamesChange(validSelectedNames);
|
|
}
|
|
} else {
|
|
console.error('Failed to load tools.');
|
|
}
|
|
} catch (error) {
|
|
console.error('Error loading tools.');
|
|
console.error(error);
|
|
} finally {
|
|
setIsLoading(false);
|
|
}
|
|
};
|
|
|
|
// Only fetch tools once when the component mounts
|
|
fetchTools();
|
|
}, [selectedToolNames, onSelectedToolNamesChange]);
|
|
|
|
const handleToggleTool = (toolName: string) => {
|
|
const newSelectedNames = selectedToolNames.includes(toolName)
|
|
? selectedToolNames.filter((name) => name !== toolName)
|
|
: [...selectedToolNames, toolName];
|
|
onSelectedToolNamesChange(newSelectedNames);
|
|
};
|
|
|
|
const selectedCount = selectedToolNames.length;
|
|
|
|
return (
|
|
<Popover className="relative">
|
|
{({ open }) => (
|
|
<>
|
|
<PopoverButton
|
|
className={cn(
|
|
'flex items-center gap-1 rounded-lg text-sm transition-colors duration-150 ease-in-out focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500',
|
|
selectedCount > 0
|
|
? 'text-[#24A0ED] hover:text-blue-200'
|
|
: 'text-black/60 hover:text-black/30 dark:text-white/60 dark:hover:*:text-white/30',
|
|
)}
|
|
title="Select Tools"
|
|
>
|
|
<Wrench size={18} />
|
|
{selectedCount > 0 ? <span> {selectedCount} </span> : null}
|
|
<ChevronDown size={16} className="opacity-60" />
|
|
</PopoverButton>
|
|
<Transition
|
|
as={Fragment}
|
|
enter="transition ease-out duration-200"
|
|
enterFrom="opacity-0 translate-y-1"
|
|
enterTo="opacity-100 translate-y-0"
|
|
leave="transition ease-in duration-150"
|
|
leaveFrom="opacity-100 translate-y-0"
|
|
leaveTo="opacity-0 translate-y-1"
|
|
>
|
|
<PopoverPanel className="absolute z-20 w-72 transform bottom-full mb-2">
|
|
<div className="overflow-hidden rounded-lg shadow-lg ring-1 ring-black/5 dark:ring-white/5 bg-white dark:bg-dark-secondary">
|
|
<div className="px-4 py-3 border-b border-light-200 dark:border-dark-200">
|
|
<h3 className="text-sm font-medium text-black/90 dark:text-white/90">
|
|
Select Tools
|
|
</h3>
|
|
<p className="text-xs text-black/60 dark:text-white/60 mt-0.5">
|
|
Choose tools to assist the AI.
|
|
</p>
|
|
</div>
|
|
{isLoading ? (
|
|
<div className="px-4 py-3">
|
|
<Loader2 className="animate-spin text-black/70 dark:text-white/70" />
|
|
</div>
|
|
) : (
|
|
<div className="max-h-60 overflow-y-auto p-1.5 space-y-0.5">
|
|
{availableTools.length === 0 && (
|
|
<p className="text-xs text-black/50 dark:text-white/50 px-2.5 py-2 text-center">
|
|
No tools available.
|
|
</p>
|
|
)}
|
|
|
|
{availableTools.map((tool) => (
|
|
<div
|
|
key={tool.name}
|
|
onClick={() => handleToggleTool(tool.name)}
|
|
className="flex items-start gap-2.5 p-2.5 rounded-md hover:bg-light-100 dark:hover:bg-dark-100 cursor-pointer"
|
|
>
|
|
{selectedToolNames.includes(tool.name) ? (
|
|
<CheckSquare
|
|
size={18}
|
|
className="text-[#24A0ED] flex-shrink-0 mt-0.5"
|
|
/>
|
|
) : (
|
|
<Square
|
|
size={18}
|
|
className="text-black/40 dark:text-white/40 flex-shrink-0 mt-0.5"
|
|
/>
|
|
)}
|
|
<div className="flex-1 min-w-0">
|
|
<span
|
|
className="text-sm font-medium text-black/80 dark:text-white/80 block truncate"
|
|
title={tool.name}
|
|
>
|
|
{tool.name.replace(/_/g, ' ')}
|
|
</span>
|
|
<p className="text-xs text-black/60 dark:text-white/60 mt-0.5">
|
|
{tool.description}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
)}
|
|
</div>
|
|
</PopoverPanel>
|
|
</Transition>
|
|
</>
|
|
)}
|
|
</Popover>
|
|
);
|
|
};
|
|
|
|
export default ToolSelector;
|