diff --git a/apps/web/package.json b/apps/web/package.json index 85514495..1d171fc8 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -19,7 +19,7 @@ "@modelcontextprotocol/sdk": "^1.11.4", "@radix-ui/react-alert-dialog": "^1.1.11", "@radix-ui/react-avatar": "^1.1.4", - "@radix-ui/react-checkbox": "1.1.2", + "@radix-ui/react-checkbox": "^1.3.3", "@radix-ui/react-collapsible": "^1.1.4", "@radix-ui/react-dialog": "^1.1.7", "@radix-ui/react-dropdown-menu": "^2.1.7", diff --git a/apps/web/src/app/(app)/onboarding/page.tsx b/apps/web/src/app/(app)/onboarding/page.tsx new file mode 100644 index 00000000..d4b26f0f --- /dev/null +++ b/apps/web/src/app/(app)/onboarding/page.tsx @@ -0,0 +1,5 @@ +import OnboardingInterface from "@/features/onboarding"; + +export default function OnboardingPage() { + return ; +} diff --git a/apps/web/src/app/api/onboarding/create-agent/route.ts b/apps/web/src/app/api/onboarding/create-agent/route.ts new file mode 100644 index 00000000..8c196b37 --- /dev/null +++ b/apps/web/src/app/api/onboarding/create-agent/route.ts @@ -0,0 +1,38 @@ +import { NextRequest, NextResponse } from "next/server"; +import { generateAgentConfigFromOnboarding } from "@/features/onboarding/agent-config-generator"; +import { OnboardingInputs } from "@/features/onboarding/types"; + +export async function POST(request: NextRequest) { + try { + const body: OnboardingInputs = await request.json(); + + // Validate required fields + if (!body.useCase || body.useCase.trim().length === 0) { + return NextResponse.json( + { error: "Use case is required" }, + { status: 400 } + ); + } + + // Generate agent configuration + const result = await generateAgentConfigFromOnboarding(body); + + if (!result.success) { + return NextResponse.json( + { error: result.error || "Failed to generate agent configuration" }, + { status: 500 } + ); + } + + return NextResponse.json({ + success: true, + agentConfig: result.agentConfig, + }); + } catch (error) { + console.error("Error in onboarding agent creation:", error); + return NextResponse.json( + { error: "Internal server error" }, + { status: 500 } + ); + } +} diff --git a/apps/web/src/components/ui/checkbox.tsx b/apps/web/src/components/ui/checkbox.tsx new file mode 100644 index 00000000..0a6a9a5b --- /dev/null +++ b/apps/web/src/components/ui/checkbox.tsx @@ -0,0 +1,28 @@ +import * as React from "react" +import * as CheckboxPrimitive from "@radix-ui/react-checkbox" +import { Check } from "lucide-react" + +import { cn } from "@/lib/utils" + +const Checkbox = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + + + + + +)) +Checkbox.displayName = CheckboxPrimitive.Root.displayName + +export { Checkbox } diff --git a/apps/web/src/features/agents/components/page-header.tsx b/apps/web/src/features/agents/components/page-header.tsx index 707dbe0a..068557a7 100644 --- a/apps/web/src/features/agents/components/page-header.tsx +++ b/apps/web/src/features/agents/components/page-header.tsx @@ -1,8 +1,9 @@ import type React from "react"; import { Button } from "@/components/ui/button"; -import { PlusCircle } from "lucide-react"; +import { PlusCircle, Sparkles } from "lucide-react"; import { CreateAgentDialog } from "./create-edit-agent-dialogs/create-agent-dialog"; import { useState } from "react"; +import { useRouter } from "next/navigation"; interface PageHeaderProps { title: string; @@ -12,6 +13,8 @@ interface PageHeaderProps { export function PageHeader({ title, description, action }: PageHeaderProps) { const [showCreateAgentDialog, setShowCreateAgentDialog] = useState(false); + const router = useRouter(); + return (
@@ -21,10 +24,19 @@ export function PageHeader({ title, description, action }: PageHeaderProps) { )}
{action || ( - +
+ + +
)} { + // This would typically call an LLM with the tool schema + // For now, we'll simulate the generation based on inputs + return await simulateAgentFieldGeneration(inputs); +} + +// Second step: Create the final agent configuration +async function createAgentConfig(generatedFields: AgentFieldGeneration): Promise { + // Create the final agent configuration + const agentConfig: GeneratedAgentConfig = { + name: generatedFields.name || "Custom Agent", + description: generatedFields.description || "A custom agent created from onboarding inputs", + config: { + model: generatedFields.model || "gpt-4o-mini", + system_prompt: generatedFields.system_prompt || "You are a helpful assistant.", + temperature: generatedFields.temperature || 0.7, + max_tokens: generatedFields.max_tokens || 4000, + tools: generatedFields.recommended_tools || [], + ...(generatedFields.rag_needed && { + rag_config: { + rag_url: typeof window !== 'undefined' ? (window as any).location.origin + '/api/rag' : "", + collections: ["general"], + }, + }), + }, + }; + + return agentConfig; +} + +// Simulate LLM-based field generation +async function simulateAgentFieldGeneration(inputs: OnboardingInputs): Promise { + // This is a simplified simulation - in reality, this would call an LLM + const useCase = inputs.useCase.toLowerCase(); + + let name = "Custom Assistant"; + let description = `An AI assistant designed for ${inputs.useCase}`; + let system_prompt = `You are a helpful AI assistant specialized in ${inputs.useCase}.`; + + // Customize based on use case + if (useCase.includes("email") || useCase.includes("communication")) { + name = "Email Assistant"; + description = "An AI assistant that helps with email management, composition, and communication tasks."; + system_prompt = `You are an expert email assistant. You help users compose professional emails, manage their inbox, and improve their communication skills. You're polite, concise, and always maintain a professional tone.`; + } else if (useCase.includes("research") || useCase.includes("analysis")) { + name = "Research Assistant"; + description = "An AI assistant that helps with research, data analysis, and information gathering."; + system_prompt = `You are a thorough research assistant. You help users gather information, analyze data, and provide well-sourced insights. You always verify information and cite sources when possible.`; + } else if (useCase.includes("content") || useCase.includes("writing")) { + name = "Content Creator"; + description = "An AI assistant that helps with content creation, writing, and creative tasks."; + system_prompt = `You are a creative content assistant. You help users write engaging content, brainstorm ideas, and improve their writing. You adapt your style to match the user's needs and audience.`; + } else if (useCase.includes("code") || useCase.includes("development")) { + name = "Code Assistant"; + description = "An AI assistant that helps with programming, code review, and development tasks."; + system_prompt = `You are an expert programming assistant. You help users write, debug, and optimize code. You provide clear explanations and follow best practices for the programming languages you work with.`; + } + + // Add industry-specific context + if (inputs.industry) { + system_prompt += ` You have specialized knowledge in the ${inputs.industry} industry.`; + } + + // Determine recommended tools based on use case + const recommended_tools = []; + if (useCase.includes("email") || useCase.includes("communication")) { + recommended_tools.push("email_tool", "calendar_tool"); + } + if (useCase.includes("research") || useCase.includes("analysis")) { + recommended_tools.push("web_search", "data_analysis_tool"); + } + if (useCase.includes("content") || useCase.includes("writing")) { + recommended_tools.push("image_generator", "grammar_checker"); + } + if (useCase.includes("code") || useCase.includes("development")) { + recommended_tools.push("code_executor", "git_tool"); + } + + return { + name, + description, + model: inputs.preferredModel || "gpt-4o-mini", + system_prompt, + temperature: 0.7, + max_tokens: 4000, + recommended_tools, + rag_needed: inputs.ragNeeded || useCase.includes("research") || useCase.includes("knowledge"), + }; +} + +// Main function to generate agent config from onboarding inputs +export async function generateAgentConfigFromOnboarding( + inputs: OnboardingInputs +): Promise { + try { + // First step: Generate agent fields + const generatedFields = await generateAgentFields(inputs); + + // Second step: Create the final agent configuration + const agentConfig = await createAgentConfig(generatedFields); + + return { + success: true, + agentConfig, + }; + } catch (error) { + return { + success: false, + error: error instanceof Error ? error.message : 'Unknown error occurred', + }; + } +} diff --git a/apps/web/src/features/onboarding/components/onboarding-form.tsx b/apps/web/src/features/onboarding/components/onboarding-form.tsx new file mode 100644 index 00000000..c9a037c3 --- /dev/null +++ b/apps/web/src/features/onboarding/components/onboarding-form.tsx @@ -0,0 +1,356 @@ +"use client"; + +import { useState } from "react"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import { Textarea } from "@/components/ui/textarea"; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; +import { Checkbox } from "@/components/ui/checkbox"; +import { LoaderCircle, ArrowRight, ArrowLeft } from "lucide-react"; +import { OnboardingInputs } from "../types"; + +interface OnboardingFormProps { + onComplete: (agentConfig: any) => void; + onCancel: () => void; +} + +const INDUSTRY_OPTIONS = [ + "Technology", + "Healthcare", + "Finance", + "Education", + "E-commerce", + "Manufacturing", + "Consulting", + "Other" +]; + +const COMPANY_SIZE_OPTIONS = [ + "1-10 employees", + "11-50 employees", + "51-200 employees", + "201-1000 employees", + "1000+ employees" +]; + +const MODEL_OPTIONS = [ + "gpt-4o-mini", + "gpt-4o", + "claude-3-5-sonnet", + "claude-3-haiku" +]; + +const COMMON_TOOLS = [ + "Email Management", + "Calendar Scheduling", + "Web Search", + "Document Analysis", + "Code Execution", + "Image Generation", + "Data Analysis", + "File Management" +]; + +export function OnboardingForm({ onComplete, onCancel }: OnboardingFormProps) { + const [currentStep, setCurrentStep] = useState(1); + const [isGenerating, setIsGenerating] = useState(false); + const [formData, setFormData] = useState({ + useCase: "", + industry: "", + companySize: "", + primaryTasks: [], + preferredModel: "gpt-4o-mini", + toolsNeeded: [], + ragNeeded: false, + teamSize: "", + budget: "" + }); + + const updateFormData = (field: keyof OnboardingInputs, value: any) => { + setFormData(prev => ({ ...prev, [field]: value })); + }; + + const handleNext = () => { + if (currentStep < 3) { + setCurrentStep(currentStep + 1); + } + }; + + const handlePrevious = () => { + if (currentStep > 1) { + setCurrentStep(currentStep - 1); + } + }; + + const handleGenerateAgent = async () => { + setIsGenerating(true); + try { + const response = await fetch("/api/onboarding/create-agent", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(formData), + }); + + const result = await response.json(); + + if (result.success && result.agentConfig) { + onComplete(result.agentConfig); + } else { + console.error("Failed to generate agent config:", result.error); + // Handle error - could show a toast or error message + } + } catch (error) { + console.error("Error generating agent:", error); + } finally { + setIsGenerating(false); + } + }; + + const isStepValid = () => { + switch (currentStep) { + case 1: + return formData.useCase.trim().length > 0; + case 2: + return formData.primaryTasks && formData.primaryTasks.length > 0; + case 3: + return true; // Optional fields + default: + return false; + } + }; + + return ( +
+ + + Create Your First Agent + + Let's build a custom AI agent tailored to your needs. This will only take a few minutes. + + + + {/* Progress indicator */} +
+ {[1, 2, 3].map((step) => ( +
+ {step} +
+ ))} +
+ + {/* Step 1: Basic Information */} + {currentStep === 1 && ( +
+
+ +