mirror of
https://github.com/rowboatlabs/rowboat.git
synced 2026-04-30 19:06:23 +02:00
145 lines
No EOL
7.9 KiB
TypeScript
145 lines
No EOL
7.9 KiB
TypeScript
'use client';
|
|
|
|
import { useState, useEffect } from "react";
|
|
import { useRouter } from 'next/navigation';
|
|
import { listTemplates } from "@/app/actions/project.actions";
|
|
import { createProjectFromTemplate } from "../lib/project-creation-utils";
|
|
import { PictureImg } from '@/components/ui/picture-img';
|
|
|
|
interface TemplatesSectionProps {}
|
|
|
|
export function TemplatesSection({}: TemplatesSectionProps) {
|
|
const [templates, setTemplates] = useState<any[]>([]);
|
|
const [templatesLoading, setTemplatesLoading] = useState(false);
|
|
const [templatesError, setTemplatesError] = useState<string | null>(null);
|
|
const router = useRouter();
|
|
|
|
// Extract unique tools from template - using same approach as ToolkitCard
|
|
const getUniqueTools = (template: any) => {
|
|
if (!template.tools) return [];
|
|
|
|
const uniqueToolsMap = new Map();
|
|
template.tools.forEach((tool: any) => {
|
|
if (!uniqueToolsMap.has(tool.name)) {
|
|
// Include all tools, following the same pattern as Composio toolkit cards
|
|
const toolData = {
|
|
name: tool.name,
|
|
isComposio: tool.isComposio,
|
|
isLibrary: tool.isLibrary,
|
|
logo: tool.isComposio && tool.composioData?.logo ? tool.composioData.logo : null,
|
|
};
|
|
|
|
uniqueToolsMap.set(tool.name, toolData);
|
|
}
|
|
});
|
|
|
|
return Array.from(uniqueToolsMap.values()).filter(tool => tool.logo); // Only show tools with logos like ToolkitCard
|
|
};
|
|
|
|
const fetchTemplates = async () => {
|
|
setTemplatesLoading(true);
|
|
setTemplatesError(null);
|
|
try {
|
|
const templatesArray = await listTemplates();
|
|
setTemplates(templatesArray);
|
|
} catch (error) {
|
|
console.error('Error fetching templates:', error);
|
|
setTemplatesError(error instanceof Error ? error.message : 'Failed to load templates');
|
|
} finally {
|
|
setTemplatesLoading(false);
|
|
}
|
|
};
|
|
|
|
// Handle template selection
|
|
const handleTemplateSelect = async (templateId: string, templateName: string) => {
|
|
await createProjectFromTemplate(templateId, router);
|
|
};
|
|
|
|
useEffect(() => {
|
|
fetchTemplates();
|
|
}, []);
|
|
|
|
return (
|
|
<div className="h-screen flex flex-col px-8 py-8 overflow-hidden">
|
|
<div className="max-w-7xl mx-auto w-full flex flex-col h-full">
|
|
<div className="px-6 pb-4">
|
|
<h2 className="text-2xl font-semibold text-gray-900 dark:text-gray-100">
|
|
Pre-built agents
|
|
</h2>
|
|
</div>
|
|
<div className="px-6 flex-1 overflow-hidden">
|
|
{templatesLoading ? (
|
|
<div className="flex items-center justify-center h-full text-sm text-gray-500 dark:text-gray-400">
|
|
Loading templates...
|
|
</div>
|
|
) : templatesError ? (
|
|
<div className="flex items-center justify-center h-full text-sm text-red-500 dark:text-red-400">
|
|
Error: {templatesError}
|
|
</div>
|
|
) : templates.length === 0 ? (
|
|
<div className="flex items-center justify-center h-full text-sm text-gray-500 dark:text-gray-400">
|
|
No templates available
|
|
</div>
|
|
) : (
|
|
<div className="h-full overflow-y-auto">
|
|
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4 pb-4">
|
|
{templates.map((template) => (
|
|
<button
|
|
key={template.id}
|
|
onClick={() => handleTemplateSelect(template.id, template.name)}
|
|
className="block p-4 border border-gray-200 dark:border-gray-700 rounded-xl hover:border-blue-300 dark:hover:border-blue-600 hover:bg-gray-50 dark:hover:bg-gray-700/50 transition-all group hover:shadow-md text-left"
|
|
>
|
|
<div className="space-y-2">
|
|
<div className="font-medium text-gray-900 dark:text-gray-100 group-hover:text-blue-600 dark:group-hover:text-blue-400 transition-colors line-clamp-1">
|
|
{template.name}
|
|
</div>
|
|
<div className="text-sm text-gray-600 dark:text-gray-400 line-clamp-2">
|
|
{template.description}
|
|
</div>
|
|
|
|
{/* Tool logos */}
|
|
{(() => {
|
|
const tools = getUniqueTools(template);
|
|
return tools.length > 0 && (
|
|
<div className="flex items-center gap-2 mt-2">
|
|
<div className="text-xs text-gray-400 dark:text-gray-500">
|
|
Tools:
|
|
</div>
|
|
<div className="flex items-center gap-1">
|
|
{tools.slice(0, 4).map((tool) => (
|
|
tool.logo && (
|
|
<PictureImg
|
|
key={tool.name}
|
|
src={tool.logo}
|
|
alt={`${tool.name} logo`}
|
|
className="w-4 h-4 rounded-sm object-cover flex-shrink-0"
|
|
title={tool.name}
|
|
/>
|
|
)
|
|
))}
|
|
{tools.length > 4 && (
|
|
<span className="text-xs text-gray-400 dark:text-gray-500">
|
|
+{tools.length - 4}
|
|
</span>
|
|
)}
|
|
</div>
|
|
</div>
|
|
);
|
|
})()}
|
|
|
|
<div className="flex items-center justify-between mt-2">
|
|
<div className="text-xs text-gray-400 dark:text-gray-500">
|
|
</div>
|
|
<div className="w-2 h-2 rounded-full bg-blue-500 opacity-75"></div>
|
|
</div>
|
|
</div>
|
|
</button>
|
|
))}
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
} |