diff --git a/ui/desktop/src/App.tsx b/ui/desktop/src/App.tsx index 9a81f4c97c..2e4119ef8c 100644 --- a/ui/desktop/src/App.tsx +++ b/ui/desktop/src/App.tsx @@ -24,7 +24,7 @@ import { TabbedPairRoute } from './components/TabbedPairRoute'; import SettingsView, { SettingsViewOptions } from './components/settings/SettingsView'; import SessionsView from './components/sessions/SessionsView'; import SharedSessionView from './components/sessions/SharedSessionView'; -import SchedulesView from './components/schedule/SchedulesView'; +import MarketplaceView from './components/marketplace/MarketplaceView'; import ProviderSettings from './components/settings/providers/ProviderSettingsPage'; import { AppLayout } from './components/Layout/AppLayout'; import { ChatProvider } from './contexts/ChatContext'; @@ -139,8 +139,7 @@ const SessionsRoute = () => { }; const SchedulesRoute = () => { - const navigate = useNavigate(); - return navigate('/')} />; + return ; }; const RecipesRoute = () => { diff --git a/ui/desktop/src/assets/Apps.png b/ui/desktop/src/assets/Apps.png new file mode 100644 index 0000000000..00c1fa9192 Binary files /dev/null and b/ui/desktop/src/assets/Apps.png differ diff --git a/ui/desktop/src/assets/GooseApp.png b/ui/desktop/src/assets/GooseApp.png new file mode 100644 index 0000000000..582bc2a988 Binary files /dev/null and b/ui/desktop/src/assets/GooseApp.png differ diff --git a/ui/desktop/src/assets/extensions.png b/ui/desktop/src/assets/extensions.png new file mode 100644 index 0000000000..9c60858e9e Binary files /dev/null and b/ui/desktop/src/assets/extensions.png differ diff --git a/ui/desktop/src/assets/g2.png b/ui/desktop/src/assets/g2.png new file mode 100644 index 0000000000..91d4607998 Binary files /dev/null and b/ui/desktop/src/assets/g2.png differ diff --git a/ui/desktop/src/assets/gooseslantIOS.png b/ui/desktop/src/assets/gooseslantIOS.png new file mode 100644 index 0000000000..fd41b3d50e Binary files /dev/null and b/ui/desktop/src/assets/gooseslantIOS.png differ diff --git a/ui/desktop/src/assets/peeps.png b/ui/desktop/src/assets/peeps.png new file mode 100644 index 0000000000..a554bfae89 Binary files /dev/null and b/ui/desktop/src/assets/peeps.png differ diff --git a/ui/desktop/src/assets/sellers.png b/ui/desktop/src/assets/sellers.png new file mode 100644 index 0000000000..f70fae1b82 Binary files /dev/null and b/ui/desktop/src/assets/sellers.png differ diff --git a/ui/desktop/src/components/GooseSidebar/AppSidebar.tsx b/ui/desktop/src/components/GooseSidebar/AppSidebar.tsx index 1076f6f0a2..260454fd08 100644 --- a/ui/desktop/src/components/GooseSidebar/AppSidebar.tsx +++ b/ui/desktop/src/components/GooseSidebar/AppSidebar.tsx @@ -73,9 +73,9 @@ const menuItems: NavigationEntry[] = [ { type: 'item', path: '/schedules', - label: 'Scheduler', + label: 'Marketplace', icon: Clock, - tooltip: 'Manage scheduled runs', + tooltip: 'Browse marketplace', }, { type: 'item', diff --git a/ui/desktop/src/components/Layout/TopNavigation.tsx b/ui/desktop/src/components/Layout/TopNavigation.tsx index c23afc6524..be72d1b0a3 100644 --- a/ui/desktop/src/components/Layout/TopNavigation.tsx +++ b/ui/desktop/src/components/Layout/TopNavigation.tsx @@ -1,6 +1,6 @@ import React, { useState, useEffect } from 'react'; import { useNavigate, useLocation } from 'react-router-dom'; -import { Home, History, FileText, Clock, Puzzle, Settings as SettingsIcon, GripVertical, Users, Hash } from 'lucide-react'; +import { Home, History, FileText, Clock, Puzzle, Settings as SettingsIcon, GripVertical, Users, Hash, ShoppingBag } from 'lucide-react'; import { motion, AnimatePresence } from 'framer-motion'; import { ChatSmart } from '../icons'; import { listSessions, getSessionInsights } from '../../api'; @@ -258,10 +258,8 @@ export const TopNavigation: React.FC = ({ isExpanded, setIsE { id: 'scheduler', path: '/schedules', - label: 'Scheduler', - icon: Clock, - getTag: () => `${scheduledTodayCount} today`, - tagAlign: 'left', + label: 'Marketplace', + icon: ShoppingBag, }, { id: 'extensions', diff --git a/ui/desktop/src/components/marketplace/MarketplaceView.tsx b/ui/desktop/src/components/marketplace/MarketplaceView.tsx new file mode 100644 index 0000000000..d38d9445d0 --- /dev/null +++ b/ui/desktop/src/components/marketplace/MarketplaceView.tsx @@ -0,0 +1,1194 @@ +import React, { useState } from 'react'; +import { motion } from 'framer-motion'; +import { + Package, + Download, + Star, + Search, + TrendingUp, + Clock +} from 'lucide-react'; +import GooseAppImage from '../../assets/GooseApp.png'; +import AppsImage from '../../assets/Apps.png'; +import G2Image from '../../assets/g2.png'; +import PeepsImage from '../../assets/peeps.png'; +import ExtensionsImage from '../../assets/extensions.png'; +import SellersImage from '../../assets/sellers.png'; + +interface MarketplaceItem { + id: string; + name: string; + description: string; + type: 'recipe' | 'extension' | 'template'; + author: string; + downloads: number; + rating: number; + lastUpdated: Date; + tags: string[]; + icon?: string; + featured?: boolean; + fullWidth?: boolean; + hero?: boolean; + gradient?: string; + tagline?: string; + image?: string; +} + +// Mock data - will be replaced with real data later +const mockItems: MarketplaceItem[] = [ + // 1. First hero card + { + id: 'hero-1', + name: 'Goose iOS', + description: 'Your AI development companion on the go. Access powerful coding assistance, run recipes, and manage your projects from anywhere. Available now on the App Store.', + type: 'extension', + author: 'Block Team', + downloads: 25680, + rating: 5.0, + lastUpdated: new Date('2024-01-29'), + tags: ['ios', 'mobile', 'app', 'hero'], + featured: true, + hero: true, + gradient: 'from-indigo-600 via-purple-600 to-pink-600', + tagline: '๐Ÿ“ฑ Now on iOS', + }, + // 2. Two featured cards + { + id: 'featured-1', + name: 'AI Code Assistant Pro', + description: 'Transform your development workflow with AI-powered code generation, intelligent refactoring, and real-time code reviews. Supports 50+ languages and frameworks.', + type: 'extension', + author: 'Block Team', + downloads: 15420, + rating: 4.9, + lastUpdated: new Date('2024-01-28'), + tags: ['ai', 'code-generation', 'productivity', 'featured'], + featured: true, + fullWidth: true, + gradient: 'from-purple-500 via-pink-500 to-red-500', + tagline: '๐Ÿš€ Most Popular Extension', + image: 'apps', + }, + { + id: 'featured-2', + name: 'Full-Stack Deployment Suite', + description: 'Deploy anywhere in seconds. One-click deployment to AWS, Azure, GCP, Vercel, and more. Includes CI/CD pipelines, monitoring, and automatic rollbacks.', + type: 'template', + author: 'Block Team', + downloads: 12890, + rating: 4.9, + lastUpdated: new Date('2024-01-27'), + tags: ['deployment', 'devops', 'cloud', 'featured'], + featured: true, + fullWidth: true, + gradient: 'from-blue-500 via-cyan-500 to-teal-500', + tagline: 'โšก Deploy in Seconds', + image: 'g2', + }, + // 3. Second hero card (full width) + { + id: 'hero-2', + name: 'Enterprise Solutions', + description: 'Scale your development with enterprise-grade tools, advanced security features, and dedicated support. Built for teams that demand the best.', + type: 'extension', + author: 'Block Team', + downloads: 18500, + rating: 5.0, + lastUpdated: new Date('2024-01-30'), + tags: ['enterprise', 'security', 'teams', 'hero'], + featured: true, + hero: true, + gradient: 'from-blue-600 to-blue-600', + tagline: '๐Ÿข For Teams', + image: 'peeps', + }, + // 4. Second set of featured cards + { + id: 'featured-3', + name: 'Cloud Infrastructure Manager', + description: 'Manage your cloud infrastructure across AWS, Azure, and GCP from a single interface. Automated provisioning, monitoring, and cost optimization.', + type: 'extension', + author: 'Block Team', + downloads: 9850, + rating: 4.8, + lastUpdated: new Date('2024-01-26'), + tags: ['cloud', 'infrastructure', 'devops', 'featured'], + featured: true, + fullWidth: true, + gradient: 'from-green-500 via-emerald-500 to-teal-500', + tagline: 'โ˜๏ธ Cloud Native', + image: 'extensions', + }, + { + id: 'featured-4', + name: 'API Testing Suite', + description: 'Comprehensive API testing with automated test generation, load testing, and real-time monitoring. Supports REST, GraphQL, and WebSocket APIs.', + type: 'template', + author: 'Block Team', + downloads: 11200, + rating: 4.9, + lastUpdated: new Date('2024-01-28'), + tags: ['testing', 'api', 'automation', 'featured'], + featured: true, + fullWidth: true, + gradient: 'from-orange-500 via-red-500 to-pink-500', + tagline: '๐Ÿงช Test Everything', + image: 'sellers', + }, + // 5. Rest of the regular tiles + { + id: '1', + name: 'Python Code Review', + description: 'Automated code review assistant for Python projects', + type: 'recipe', + author: 'Block Team', + downloads: 1250, + rating: 4.8, + lastUpdated: new Date('2024-01-15'), + tags: ['python', 'code-review', 'quality'], + }, + { + id: '2', + name: 'React Component Generator', + description: 'Generate React components with TypeScript and tests', + type: 'recipe', + author: 'Community', + downloads: 890, + rating: 4.5, + lastUpdated: new Date('2024-01-10'), + tags: ['react', 'typescript', 'components'], + }, + { + id: '3', + name: 'API Documentation Writer', + description: 'Automatically generate API documentation from code', + type: 'recipe', + author: 'Block Team', + downloads: 2100, + rating: 4.9, + lastUpdated: new Date('2024-01-20'), + tags: ['documentation', 'api', 'openapi'], + }, + { + id: '4', + name: 'Git Workflow Helper', + description: 'Streamline your git workflow with smart commit messages and branch management', + type: 'recipe', + author: 'Community', + downloads: 1580, + rating: 4.7, + lastUpdated: new Date('2024-01-18'), + tags: ['git', 'workflow', 'automation'], + }, + { + id: '5', + name: 'Database Schema Designer', + description: 'Design and generate database schemas with best practices', + type: 'extension', + author: 'Block Team', + downloads: 2340, + rating: 4.9, + lastUpdated: new Date('2024-01-22'), + tags: ['database', 'schema', 'sql'], + }, + { + id: '6', + name: 'Test Suite Generator', + description: 'Automatically generate comprehensive test suites for your code', + type: 'recipe', + author: 'Community', + downloads: 1120, + rating: 4.6, + lastUpdated: new Date('2024-01-12'), + tags: ['testing', 'automation', 'quality'], + }, + { + id: '7', + name: 'REST API Builder', + description: 'Scaffold complete REST APIs with authentication and validation', + type: 'template', + author: 'Block Team', + downloads: 3200, + rating: 4.8, + lastUpdated: new Date('2024-01-25'), + tags: ['api', 'rest', 'backend'], + }, + { + id: '8', + name: 'Docker Compose Generator', + description: 'Create optimized Docker Compose configurations for your stack', + type: 'recipe', + author: 'Community', + downloads: 1890, + rating: 4.7, + lastUpdated: new Date('2024-01-19'), + tags: ['docker', 'devops', 'containers'], + }, + { + id: '9', + name: 'GraphQL Schema Builder', + description: 'Design and implement GraphQL schemas with resolvers', + type: 'extension', + author: 'Block Team', + downloads: 1450, + rating: 4.6, + lastUpdated: new Date('2024-01-16'), + tags: ['graphql', 'api', 'schema'], + }, + { + id: '10', + name: 'CI/CD Pipeline Creator', + description: 'Generate CI/CD pipelines for GitHub Actions, GitLab, and more', + type: 'recipe', + author: 'Community', + downloads: 2670, + rating: 4.9, + lastUpdated: new Date('2024-01-23'), + tags: ['ci-cd', 'devops', 'automation'], + }, + { + id: '11', + name: 'Microservices Template', + description: 'Complete microservices architecture with service mesh', + type: 'template', + author: 'Block Team', + downloads: 2890, + rating: 4.8, + lastUpdated: new Date('2024-01-21'), + tags: ['microservices', 'architecture', 'kubernetes'], + }, + { + id: '12', + name: 'Code Refactoring Assistant', + description: 'Intelligent code refactoring suggestions and automated improvements', + type: 'recipe', + author: 'Community', + downloads: 1340, + rating: 4.5, + lastUpdated: new Date('2024-01-14'), + tags: ['refactoring', 'code-quality', 'optimization'], + }, + { + id: '13', + name: 'Security Audit Tool', + description: 'Scan your codebase for security vulnerabilities and best practices', + type: 'extension', + author: 'Block Team', + downloads: 3450, + rating: 4.9, + lastUpdated: new Date('2024-01-24'), + tags: ['security', 'audit', 'vulnerability'], + }, + { + id: '14', + name: 'TypeScript Migrator', + description: 'Migrate JavaScript projects to TypeScript with type inference', + type: 'recipe', + author: 'Community', + downloads: 1670, + rating: 4.7, + lastUpdated: new Date('2024-01-17'), + tags: ['typescript', 'migration', 'javascript'], + }, + { + id: '15', + name: 'Mobile App Starter', + description: 'React Native starter template with navigation and state management', + type: 'template', + author: 'Block Team', + downloads: 2120, + rating: 4.6, + lastUpdated: new Date('2024-01-13'), + tags: ['react-native', 'mobile', 'starter'], + }, + { + id: '16', + name: 'Performance Optimizer', + description: 'Analyze and optimize application performance bottlenecks', + type: 'recipe', + author: 'Community', + downloads: 1980, + rating: 4.8, + lastUpdated: new Date('2024-01-20'), + tags: ['performance', 'optimization', 'profiling'], + }, + { + id: '17', + name: 'Kubernetes Deployer', + description: 'Generate Kubernetes manifests and deployment strategies', + type: 'extension', + author: 'Block Team', + downloads: 2560, + rating: 4.7, + lastUpdated: new Date('2024-01-22'), + tags: ['kubernetes', 'deployment', 'devops'], + }, + { + id: '18', + name: 'Accessibility Checker', + description: 'Ensure your web apps meet WCAG accessibility standards', + type: 'recipe', + author: 'Community', + downloads: 1230, + rating: 4.6, + lastUpdated: new Date('2024-01-11'), + tags: ['accessibility', 'a11y', 'wcag'], + }, + { + id: '19', + name: 'Serverless Framework', + description: 'Deploy serverless functions to AWS Lambda, Azure, and GCP', + type: 'template', + author: 'Block Team', + downloads: 2780, + rating: 4.8, + lastUpdated: new Date('2024-01-26'), + tags: ['serverless', 'lambda', 'cloud'], + }, + { + id: '20', + name: 'E2E Test Automation', + description: 'Playwright and Cypress test automation for web applications', + type: 'recipe', + author: 'Community', + downloads: 1560, + rating: 4.7, + lastUpdated: new Date('2024-01-19'), + tags: ['testing', 'e2e', 'automation'], + }, + { + id: '21', + name: 'Monorepo Manager', + description: 'Manage monorepos with Nx, Turborepo, or Lerna', + type: 'extension', + author: 'Block Team', + downloads: 2140, + rating: 4.6, + lastUpdated: new Date('2024-01-21'), + tags: ['monorepo', 'workspace', 'tooling'], + }, + { + id: '22', + name: 'CSS Framework Generator', + description: 'Generate custom Tailwind, Bootstrap, or Material UI themes', + type: 'recipe', + author: 'Community', + downloads: 1890, + rating: 4.5, + lastUpdated: new Date('2024-01-15'), + tags: ['css', 'styling', 'themes'], + }, + { + id: '23', + name: 'Data Migration Tool', + description: 'Migrate data between databases with validation and rollback', + type: 'extension', + author: 'Block Team', + downloads: 1720, + rating: 4.8, + lastUpdated: new Date('2024-01-23'), + tags: ['database', 'migration', 'etl'], + }, + { + id: '24', + name: 'Logging & Monitoring', + description: 'Integrate structured logging and monitoring solutions', + type: 'recipe', + author: 'Community', + downloads: 2340, + rating: 4.7, + lastUpdated: new Date('2024-01-20'), + tags: ['logging', 'monitoring', 'observability'], + }, + { + id: '25', + name: 'OAuth Provider Setup', + description: 'Implement OAuth 2.0 authentication with multiple providers', + type: 'template', + author: 'Block Team', + downloads: 3120, + rating: 4.9, + lastUpdated: new Date('2024-01-25'), + tags: ['auth', 'oauth', 'security'], + }, + { + id: '26', + name: 'Markdown Documentation', + description: 'Generate beautiful documentation sites from markdown files', + type: 'recipe', + author: 'Community', + downloads: 1450, + rating: 4.6, + lastUpdated: new Date('2024-01-16'), + tags: ['documentation', 'markdown', 'static-site'], + }, + { + id: '27', + name: 'WebSocket Server', + description: 'Real-time WebSocket server with Socket.io or native WS', + type: 'template', + author: 'Block Team', + downloads: 1980, + rating: 4.7, + lastUpdated: new Date('2024-01-22'), + tags: ['websocket', 'realtime', 'socket.io'], + }, + { + id: '28', + name: 'Code Formatter', + description: 'Auto-format code with Prettier, ESLint, and custom rules', + type: 'recipe', + author: 'Community', + downloads: 2890, + rating: 4.8, + lastUpdated: new Date('2024-01-24'), + tags: ['formatting', 'linting', 'code-quality'], + }, + { + id: '29', + name: 'Feature Flag Manager', + description: 'Implement feature flags with LaunchDarkly or custom solution', + type: 'extension', + author: 'Block Team', + downloads: 1670, + rating: 4.6, + lastUpdated: new Date('2024-01-18'), + tags: ['feature-flags', 'deployment', 'configuration'], + }, + { + id: '30', + name: 'Email Template Builder', + description: 'Create responsive email templates with MJML or React Email', + type: 'recipe', + author: 'Community', + downloads: 1340, + rating: 4.5, + lastUpdated: new Date('2024-01-14'), + tags: ['email', 'templates', 'mjml'], + }, + { + id: '31', + name: 'Payment Integration', + description: 'Integrate Stripe, PayPal, or Square payment processing', + type: 'template', + author: 'Block Team', + downloads: 3450, + rating: 4.9, + lastUpdated: new Date('2024-01-27'), + tags: ['payments', 'stripe', 'ecommerce'], + }, + { + id: '32', + name: 'Caching Strategy', + description: 'Implement Redis, Memcached, or in-memory caching', + type: 'recipe', + author: 'Community', + downloads: 2120, + rating: 4.7, + lastUpdated: new Date('2024-01-21'), + tags: ['caching', 'redis', 'performance'], + }, + { + id: '33', + name: 'Internationalization', + description: 'Add i18n support with react-intl or next-i18next', + type: 'extension', + author: 'Block Team', + downloads: 1890, + rating: 4.6, + lastUpdated: new Date('2024-01-19'), + tags: ['i18n', 'localization', 'translation'], + }, + { + id: '34', + name: 'Error Tracking', + description: 'Integrate Sentry, Rollbar, or custom error tracking', + type: 'recipe', + author: 'Community', + downloads: 2560, + rating: 4.8, + lastUpdated: new Date('2024-01-23'), + tags: ['errors', 'monitoring', 'sentry'], + }, + { + id: '35', + name: 'Admin Dashboard', + description: 'Full-featured admin dashboard with charts and tables', + type: 'template', + author: 'Block Team', + downloads: 3890, + rating: 4.9, + lastUpdated: new Date('2024-01-26'), + tags: ['admin', 'dashboard', 'ui'], + }, + { + id: '36', + name: 'Rate Limiter', + description: 'API rate limiting with Redis or in-memory storage', + type: 'recipe', + author: 'Community', + downloads: 1780, + rating: 4.7, + lastUpdated: new Date('2024-01-17'), + tags: ['rate-limiting', 'api', 'security'], + }, + { + id: '37', + name: 'Search Engine', + description: 'Integrate Elasticsearch, Algolia, or MeiliSearch', + type: 'extension', + author: 'Block Team', + downloads: 2340, + rating: 4.8, + lastUpdated: new Date('2024-01-24'), + tags: ['search', 'elasticsearch', 'indexing'], + }, + { + id: '38', + name: 'Image Optimizer', + description: 'Optimize and transform images with Sharp or Cloudinary', + type: 'recipe', + author: 'Community', + downloads: 1560, + rating: 4.6, + lastUpdated: new Date('2024-01-15'), + tags: ['images', 'optimization', 'cdn'], + }, + { + id: '39', + name: 'Queue Worker', + description: 'Background job processing with Bull, BullMQ, or SQS', + type: 'template', + author: 'Block Team', + downloads: 2670, + rating: 4.8, + lastUpdated: new Date('2024-01-25'), + tags: ['queue', 'workers', 'background-jobs'], + }, + { + id: '40', + name: 'Form Builder', + description: 'Dynamic form builder with validation and conditional logic', + type: 'recipe', + author: 'Community', + downloads: 1920, + rating: 4.5, + lastUpdated: new Date('2024-01-18'), + tags: ['forms', 'validation', 'ui'], + }, + { + id: '41', + name: 'Notification System', + description: 'Multi-channel notifications: email, SMS, push, in-app', + type: 'extension', + author: 'Block Team', + downloads: 2890, + rating: 4.9, + lastUpdated: new Date('2024-01-26'), + tags: ['notifications', 'messaging', 'alerts'], + }, + { + id: '42', + name: 'PDF Generator', + description: 'Generate PDFs from HTML with Puppeteer or PDFKit', + type: 'recipe', + author: 'Community', + downloads: 1450, + rating: 4.6, + lastUpdated: new Date('2024-01-16'), + tags: ['pdf', 'documents', 'generation'], + }, + { + id: '43', + name: 'Analytics Dashboard', + description: 'Custom analytics with Google Analytics, Mixpanel, or Amplitude', + type: 'template', + author: 'Block Team', + downloads: 3120, + rating: 4.8, + lastUpdated: new Date('2024-01-27'), + tags: ['analytics', 'tracking', 'metrics'], + }, + { + id: '44', + name: 'API Versioning', + description: 'Implement API versioning strategies and deprecation', + type: 'recipe', + author: 'Community', + downloads: 1680, + rating: 4.7, + lastUpdated: new Date('2024-01-20'), + tags: ['api', 'versioning', 'backwards-compatibility'], + }, + { + id: '45', + name: 'State Machine', + description: 'Implement state machines with XState or custom logic', + type: 'extension', + author: 'Block Team', + downloads: 1340, + rating: 4.6, + lastUpdated: new Date('2024-01-19'), + tags: ['state-machine', 'workflow', 'xstate'], + }, + { + id: '46', + name: 'Blog Platform', + description: 'Complete blog platform with CMS, comments, and SEO', + type: 'template', + author: 'Community', + downloads: 2450, + rating: 4.7, + lastUpdated: new Date('2024-01-22'), + tags: ['blog', 'cms', 'content'], + }, + { + id: '47', + name: 'Backup Automation', + description: 'Automated backups for databases and file systems', + type: 'recipe', + author: 'Block Team', + downloads: 1890, + rating: 4.8, + lastUpdated: new Date('2024-01-24'), + tags: ['backup', 'disaster-recovery', 'automation'], + }, + { + id: '48', + name: 'Chat Application', + description: 'Real-time chat with rooms, DMs, and file sharing', + type: 'template', + author: 'Community', + downloads: 3340, + rating: 4.9, + lastUpdated: new Date('2024-01-28'), + tags: ['chat', 'messaging', 'realtime'], + }, +]; + +const HeroCard: React.FC<{ + item: MarketplaceItem; + onInstall: (item: MarketplaceItem) => void; +}> = ({ item, onInstall }) => { + const [isHovered, setIsHovered] = useState(false); + + const formatDownloads = (count: number) => { + if (count >= 1000) return `${(count / 1000).toFixed(1)}k`; + return count.toString(); + }; + + return ( + setIsHovered(true)} + onHoverEnd={() => setIsHovered(false)} + className={` + relative cursor-pointer overflow-hidden + col-span-full + rounded-2xl + bg-background-default + min-h-[640px] md:min-h-[500px] sm:min-h-[700px] + `} + > + + {/* Tagline badge - Top left */} + {item.tagline && ( +
+ {item.tagline} +
+ )} + + {/* Content - Left side with text - Bottom aligned on desktop, top on mobile */} +
+

+ {item.name} +

+

+ {item.description} +

+ + {/* Stats */} +
+
+ + {formatDownloads(item.downloads)} downloads +
+
+ + {item.rating} rating +
+
+ {item.type} +
+
+
+ + {/* Hero image - responsive positioning */} + {/* Different images based on item.image property */} + {item.image === 'peeps' ? ( + // Custom image positioned on the right bottom (like peeps.png for Enterprise Solutions) +
+ {item.name} +
+ ) : item.image ? ( + // Other custom images - full width +
+ {item.name} +
+ ) : ( + // Default phone image with bottom cutoff (for Goose iOS) +
+ Goose App +
+ )} + + {/* Install button - bottom right */} + { + e.stopPropagation(); + onInstall(item); + }} + className="absolute bottom-6 right-6 md:bottom-12 md:right-12 z-20 w-12 h-12 md:w-16 md:h-16 rounded-full bg-background-accent text-text-on-accent hover:bg-background-medium hover:text-text-default transition-colors flex items-center justify-center" + > + + +
+ ); +}; + +const FeaturedCard: React.FC<{ + item: MarketplaceItem; + onInstall: (item: MarketplaceItem) => void; +}> = ({ item, onInstall }) => { + const [isHovered, setIsHovered] = useState(false); + + const formatDownloads = (count: number) => { + if (count >= 1000) return `${(count / 1000).toFixed(1)}k`; + return count.toString(); + }; + + return ( + setIsHovered(true)} + onHoverEnd={() => setIsHovered(false)} + className={` + relative cursor-pointer overflow-hidden + rounded-2xl + bg-background-default + flex flex-col + min-h-[400px] + `} + > + {/* Image Section - Takes up most of the space */} +
+ {/* Show actual image if provided, otherwise show gradient with pattern */} + {item.image === 'apps' ? ( + {item.name} + ) : item.image === 'g2' ? ( + {item.name} + ) : item.image === 'peeps' ? ( + {item.name} + ) : item.image === 'extensions' ? ( + {item.name} + ) : item.image === 'sellers' ? ( + {item.name} + ) : ( + /* Animated background pattern for gradient cards */ +
+
+
+ )} + + {/* Tagline badge - top left */} + {item.tagline && ( +
+ {item.tagline} +
+ )} + + {/* Install button - top right */} + { + e.stopPropagation(); + onInstall(item); + }} + className="absolute top-4 right-4 z-10 w-10 h-10 rounded-full text-white hover:bg-white hover:text-gray-900 transition-colors flex items-center justify-center" + > + + +
+ + {/* Text Content Section - Compact at bottom */} +
+
+

+ {item.type} +

+

+ {item.name} +

+
+ +

+ {item.description} +

+ + {/* Stats at bottom */} +
+
+ + {item.rating} +
+
+ + {formatDownloads(item.downloads)} +
+
+
+ + ); +}; + +const MarketplaceCard: React.FC<{ + item: MarketplaceItem; + onInstall: (item: MarketplaceItem) => void; +}> = ({ item, onInstall }) => { + const [isHovered, setIsHovered] = useState(false); + + const formatDownloads = (count: number) => { + if (count >= 1000) return `${(count / 1000).toFixed(1)}k`; + return count.toString(); + }; + + const formatDate = (date: Date) => { + const now = new Date(); + const diff = now.getTime() - date.getTime(); + const days = Math.floor(diff / (1000 * 60 * 60 * 24)); + + if (days === 0) return 'Today'; + if (days === 1) return 'Yesterday'; + if (days < 7) return `${days}d ago`; + if (days < 30) return `${Math.floor(days / 7)}w ago`; + return `${Math.floor(days / 30)}mo ago`; + }; + + return ( + setIsHovered(true)} + onHoverEnd={() => setIsHovered(false)} + className=" + relative cursor-pointer group + bg-background-default + px-6 py-6 + transition-colors duration-200 + hover:bg-background-medium + aspect-square + flex flex-col justify-between + rounded-2xl + " + > + {/* Icon/Type indicator in top left */} +
+
+ +
+ + {/* Type badge */} +
+ {item.type} +
+
+ + {/* Rating in top right */} +
+ + {item.rating} +
+ + {/* Install button (visible on hover) */} + { + e.stopPropagation(); + onInstall(item); + }} + className="absolute top-14 right-4 px-3 py-1.5 rounded-lg bg-green-500 text-white text-sm font-medium hover:bg-green-600 transition-colors flex items-center gap-1" + > + + Install + + + {/* Content at bottom */} +
+

+ {item.name} +

+

+ {item.description} +

+ + {/* Stats row */} +
+
+ + {formatDownloads(item.downloads)} +
+
+ + {formatDate(item.lastUpdated)} +
+
+ + {/* Author */} +

+ by {item.author} +

+
+
+ ); +}; + +const EmptyMarketplaceTile: React.FC = () => { + return ( +
+
+
+
+
+ ); +}; + +const MarketplaceView: React.FC = () => { + const [searchQuery, setSearchQuery] = useState(''); + const [selectedFilter, setSelectedFilter] = useState<'all' | 'recipe' | 'extension' | 'template'>('all'); + const [items] = useState(mockItems); + + const filteredItems = items.filter(item => { + const matchesSearch = item.name.toLowerCase().includes(searchQuery.toLowerCase()) || + item.description.toLowerCase().includes(searchQuery.toLowerCase()) || + item.tags.some(tag => tag.toLowerCase().includes(searchQuery.toLowerCase())); + const matchesFilter = selectedFilter === 'all' || item.type === selectedFilter; + return matchesSearch && matchesFilter; + }); + + const handleInstall = (item: MarketplaceItem) => { + console.log('Installing:', item); + // TODO: Implement installation logic + }; + + // Calculate empty tiles to fill viewport + const calculateEmptyTiles = (itemCount: number) => { + const minTiles = 12; // Minimum 2 rows + return Math.max(0, minTiles - itemCount); + }; + + return ( +
+ {/* Header Section */} +
+
+
+

Marketplace

+

Discover recipes, extensions, and templates

+
+
+ +
+
+ + {/* Search and Filters */} +
+
+ + setSearchQuery(e.target.value)} + placeholder="Search marketplace..." + className="w-full pl-10 pr-4 py-2 rounded-lg border border-border-default bg-background-muted focus:outline-none focus:ring-2 focus:ring-background-accent text-sm" + /> +
+ + {/* Filter buttons */} +
+ {(['all', 'recipe', 'extension', 'template'] as const).map((filter) => ( + + ))} +
+
+
+ + {/* Content */} +
+ {/* Render items in order, grouping featured cards together */} + {(() => { + const result: JSX.Element[] = []; + let featuredBuffer: MarketplaceItem[] = []; + + filteredItems.forEach((item, index) => { + if (item.hero) { + // Hero card - always full width + // Flush any buffered featured cards first + if (featuredBuffer.length > 0) { + result.push( +
+ {featuredBuffer.map((featuredItem) => ( + + ))} +
+ ); + featuredBuffer = []; + } + // Render hero card + result.push( +
+ +
+ ); + } else if (item.fullWidth) { + // Buffer featured cards (half-width cards in 2-column grid) + featuredBuffer.push(item); + } else { + // Flush any buffered featured cards first + if (featuredBuffer.length > 0) { + result.push( +
+ {featuredBuffer.map((featuredItem) => ( + + ))} +
+ ); + featuredBuffer = []; + } + } + }); + + // Flush any remaining featured cards + if (featuredBuffer.length > 0) { + result.push( +
+ {featuredBuffer.map((featuredItem) => ( + + ))} +
+ ); + } + + return result; + })()} + + {/* Regular Tiles Section */} + {filteredItems.filter(item => !item.hero && !item.fullWidth).length > 0 && ( +
+ {filteredItems + .filter(item => !item.hero && !item.fullWidth) + .map((item) => ( + + ))} + + {/* Empty tiles */} + {Array.from({ length: calculateEmptyTiles(filteredItems.filter(i => !i.hero && !i.fullWidth).length) }).map((_, index) => ( + + ))} +
+ )} + + {/* Empty state */} + {filteredItems.length === 0 && ( +
+ +

No items found

+

+ Try adjusting your search or filters +

+
+ )} +
+
+ ); +}; + +export default MarketplaceView; diff --git a/ui/desktop/src/components/recipes/CreateRecipeForm.tsx b/ui/desktop/src/components/recipes/CreateRecipeForm.tsx index b7093f73df..afbc6876a2 100644 --- a/ui/desktop/src/components/recipes/CreateRecipeForm.tsx +++ b/ui/desktop/src/components/recipes/CreateRecipeForm.tsx @@ -1,15 +1,18 @@ import { useState, useEffect } from 'react'; import { useForm } from '@tanstack/react-form'; import { z } from 'zod'; -import { FileText } from 'lucide-react'; +import { FileText, Clock, ChevronDown, ChevronUp, AlertCircle } from 'lucide-react'; +import { motion, AnimatePresence } from 'framer-motion'; import { Button } from '../ui/button'; import { Recipe } from '../../recipe'; -import { saveRecipe } from '../../recipe/recipeStorage'; +import { saveRecipe, listSavedRecipes } from '../../recipe/recipeStorage'; import { toastSuccess, toastError } from '../../toasts'; import { useEscapeKey } from '../../hooks/useEscapeKey'; import { RecipeNameField, recipeNameSchema } from './shared/RecipeNameField'; import { generateRecipeNameFromTitle } from './shared/recipeNameUtils'; import { validateJsonSchema, getValidationErrorMessages } from '../../recipe/validation'; +import { createSchedule } from '../../schedule'; +import cronstrue from 'cronstrue'; interface CreateRecipeFormProps { isOpen: boolean; @@ -27,14 +30,53 @@ const createRecipeSchema = z.object({ jsonSchema: z.string(), recipeName: recipeNameSchema, global: z.boolean(), + enableSchedule: z.boolean(), + cronExpression: z.string(), + scheduleId: z.string(), }); export default function CreateRecipeForm({ isOpen, onClose, onSuccess }: CreateRecipeFormProps) { const [creating, setCreating] = useState(false); + const [showAdvanced, setShowAdvanced] = useState(false); + const [cronPreview, setCronPreview] = useState(''); + const [existingRecipes, setExistingRecipes] = useState([]); + const [recipeNameWarning, setRecipeNameWarning] = useState(''); // Handle Esc key for modal useEscapeKey(isOpen, onClose); + // Load existing recipe names when modal opens + useEffect(() => { + if (isOpen) { + listSavedRecipes().then((recipes) => { + setExistingRecipes(recipes.map((r) => r.name)); + }).catch((err) => { + console.error('Failed to load existing recipes:', err); + }); + } + }, [isOpen]); + + // Check for duplicate recipe names + const checkForDuplicate = (recipeName: string): string => { + if (!recipeName) return recipeName; + + let finalName = recipeName; + let counter = 1; + + while (existingRecipes.includes(finalName)) { + finalName = `${recipeName}-${counter}`; + counter++; + } + + if (finalName !== recipeName) { + setRecipeNameWarning(`Recipe name "${recipeName}" already exists. Using "${finalName}" instead.`); + } else { + setRecipeNameWarning(''); + } + + return finalName; + }; + const createRecipeForm = useForm({ defaultValues: { title: '', @@ -45,9 +87,12 @@ export default function CreateRecipeForm({ isOpen, onClose, onSuccess }: CreateR jsonSchema: '', recipeName: '', global: true, + enableSchedule: false, + cronExpression: '0 0 * * *', // Daily at midnight + scheduleId: '', }, validators: { - onChange: createRecipeSchema, + onSubmit: createRecipeSchema, }, onSubmit: async ({ value }) => { setCreating(true); @@ -86,11 +131,44 @@ export default function CreateRecipeForm({ isOpen, onClose, onSuccess }: CreateR response: jsonSchemaObj ? { json_schema: jsonSchemaObj } : undefined, }; - await saveRecipe(recipe, { + const savedRecipePath = await saveRecipe(recipe, { name: value.recipeName.trim(), global: value.global, }); + // Create schedule if enabled + if (value.enableSchedule && value.cronExpression && value.scheduleId) { + try { + // Get the recipe path - construct it based on global/local + const recipeSource = value.global + ? `~/.config/goose/recipes/${value.recipeName.trim()}.yaml` + : `.goose/recipes/${value.recipeName.trim()}.yaml`; + + await createSchedule({ + id: value.scheduleId.trim(), + recipe_source: recipeSource, + cron: value.cronExpression.trim(), + }); + + toastSuccess({ + title: value.recipeName.trim(), + msg: 'Recipe and schedule created successfully', + }); + } catch (scheduleError) { + console.error('Failed to create schedule:', scheduleError); + toastError({ + title: 'Schedule Creation Failed', + msg: 'Recipe was created but schedule failed. You can create the schedule from the Scheduler view.', + traceback: scheduleError instanceof Error ? scheduleError.message : String(scheduleError), + }); + } + } else { + toastSuccess({ + title: value.recipeName.trim(), + msg: 'Recipe created successfully', + }); + } + // Reset dialog state createRecipeForm.reset({ title: '', @@ -101,15 +179,14 @@ export default function CreateRecipeForm({ isOpen, onClose, onSuccess }: CreateR jsonSchema: '', recipeName: '', global: true, + enableSchedule: false, + cronExpression: '0 0 * * *', + scheduleId: '', }); + setCronPreview(''); onClose(); onSuccess(); - - toastSuccess({ - title: value.recipeName.trim(), - msg: 'Recipe created successfully', - }); } catch (error) { console.error('Failed to create recipe:', error); @@ -124,40 +201,26 @@ export default function CreateRecipeForm({ isOpen, onClose, onSuccess }: CreateR }, }); - // Set default example values when the modal opens + // Clear form when modal opens useEffect(() => { if (isOpen) { - // Set example values like the original did - createRecipeForm.setFieldValue('title', 'Python Development Assistant'); - createRecipeForm.setFieldValue( - 'description', - 'A helpful assistant for Python development tasks including coding, debugging, and code review.' - ); - createRecipeForm.setFieldValue( - 'instructions', - `You are an expert Python developer assistant. Help users with: - -1. Writing clean, efficient Python code -2. Debugging and troubleshooting issues -3. Code review and optimization suggestions -4. Best practices and design patterns -5. Testing and documentation - -Always provide clear explanations and working code examples. - -Parameters you can use: -- {{project_type}}: The type of Python project (web, data science, CLI, etc.) -- {{python_version}}: Target Python version` - ); - createRecipeForm.setFieldValue( - 'prompt', - 'What Python development task can I help you with today?' - ); - createRecipeForm.setFieldValue('activities', 'coding, debugging, testing, documentation'); - createRecipeForm.setFieldValue('recipeName', 'python-development-assistant'); - createRecipeForm.setFieldValue('global', true); + // Start with empty fields + createRecipeForm.reset({ + title: '', + description: '', + instructions: '', + prompt: '', + activities: '', + jsonSchema: '', + recipeName: '', + global: true, + enableSchedule: false, + cronExpression: '0 0 * * *', + scheduleId: '', + }); + setRecipeNameWarning(''); } - }, [isOpen, createRecipeForm]); + }, [isOpen]); const handleClose = () => { // Reset form to default values @@ -170,7 +233,12 @@ Parameters you can use: jsonSchema: '', recipeName: '', global: true, + enableSchedule: false, + cronExpression: '0 0 * * *', + scheduleId: '', }); + setCronPreview(''); + setRecipeNameWarning(''); onClose(); }; @@ -205,14 +273,18 @@ Parameters you can use: onChange={(e) => { const value = e.target.value; field.handleChange(value); - // Auto-generate recipe name when title changes + // Auto-generate recipe name when title changes and check for duplicates if (value.trim()) { const suggestedName = generateRecipeNameFromTitle(value); - createRecipeForm.setFieldValue('recipeName', suggestedName); + const finalName = checkForDuplicate(suggestedName); + createRecipeForm.setFieldValue('recipeName', finalName); + } else { + createRecipeForm.setFieldValue('recipeName', ''); + setRecipeNameWarning(''); } }} onBlur={field.handleBlur} - className={`w-full p-3 border rounded-lg bg-background-default text-text-standard focus:outline-none focus:ring-2 focus:ring-blue-500 ${ + className={`w-full p-3 border rounded-lg bg-background-default text-text-standard focus:outline-none focus:ring-2 focus:ring-blue-500 placeholder:text-text-muted/50 ${ field.state.meta.errors.length > 0 ? 'border-red-500' : 'border-border-subtle' }`} placeholder="Recipe title" @@ -225,6 +297,12 @@ Parameters you can use: : field.state.meta.errors[0]?.message || String(field.state.meta.errors[0])}

)} + {recipeNameWarning && ( +
+ +

{recipeNameWarning}

+
+ )}
)} @@ -244,7 +322,7 @@ Parameters you can use: value={field.state.value} onChange={(e) => field.handleChange(e.target.value)} onBlur={field.handleBlur} - className={`w-full p-3 border rounded-lg bg-background-default text-text-standard focus:outline-none focus:ring-2 focus:ring-blue-500 ${ + className={`w-full p-3 border rounded-lg bg-background-default text-text-standard focus:outline-none focus:ring-2 focus:ring-blue-500 placeholder:text-text-muted/50 ${ field.state.meta.errors.length > 0 ? 'border-red-500' : 'border-border-subtle' }`} placeholder="Brief description of what this recipe does" @@ -274,15 +352,17 @@ Parameters you can use: value={field.state.value} onChange={(e) => field.handleChange(e.target.value)} onBlur={field.handleBlur} - className={`w-full p-3 border rounded-lg bg-background-default text-text-standard focus:outline-none focus:ring-2 focus:ring-blue-500 resize-none font-mono text-sm ${ + className={`w-full p-3 border rounded-lg bg-background-default text-text-standard focus:outline-none focus:ring-2 focus:ring-blue-500 resize-none font-mono text-sm placeholder:text-text-muted/50 ${ field.state.meta.errors.length > 0 ? 'border-red-500' : 'border-border-subtle' }`} placeholder="Detailed instructions for the AI agent..." rows={8} /> -

- Use {`{{parameter_name}}`} to define parameters that users can fill in -

+ {field.state.value && ( +

+ Use {`{{parameter_name}}`} to define parameters that users can fill in +

+ )} {field.state.meta.errors.length > 0 && (

{typeof field.state.meta.errors[0] === 'string' @@ -294,71 +374,220 @@ Parameters you can use: )} - + {/* Recipe name is auto-generated from title - hidden from UI but kept in form state */} + {(field) => ( -

-