"use client" import { useState } from "react" import Link from "next/link" import { useUser } from "@stackframe/stack" import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card" import { Button } from "@/components/ui/button" import { Badge } from "@/components/ui/badge" import { Input } from "@/components/ui/input" import { Label } from "@/components/ui/label" import { Textarea } from "@/components/ui/textarea" import { ModelSelector } from "@/components/model-selector" import { DEFAULT_MODEL } from "@/types/prompt" import { Workflow, Plus, Play, ArrowRight, Trash2, ChevronDown, ChevronUp, Loader2, Copy, Check, Sparkles, PenLine, Search, Share2, Save, Globe, Library, Lightbulb, Repeat } from "lucide-react" import ReactMarkdown from "react-markdown" import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select" interface WorkflowStep { id: string name: string prompt: string output?: string isLoading?: boolean iterations?: number // Loop count (1 = run once, 2+ = repeat) } // Preset workflow templates const WORKFLOW_TEMPLATES = [ { name: "Blog Post Creator", description: "Generate a complete blog post from a topic", steps: [ { name: "Topic Research", prompt: "Research and outline key points about: {{topic}}" }, { name: "Write Draft", prompt: "Using this research:\n{{previous_output}}\n\nWrite a detailed blog post about {{topic}}" }, { name: "SEO Optimize", prompt: "Optimize this content for SEO:\n{{previous_output}}\n\nAdd meta description, keywords, and improve headings." }, ] }, { name: "Content Repurposer", description: "Turn one piece of content into multiple formats", steps: [ { name: "Summarize", prompt: "Summarize the key points from:\n{{input}}" }, { name: "Twitter Thread", prompt: "Turn this into a Twitter thread (5-7 tweets):\n{{previous_output}}" }, { name: "LinkedIn Post", prompt: "Create a LinkedIn post from this:\n{{previous_output}}" }, ] }, { name: "Product Launch", description: "Create launch materials for a product", steps: [ { name: "Product Description", prompt: "Write a compelling product description for: {{product}}" }, { name: "Email Announcement", prompt: "Create a launch email based on:\n{{previous_output}}" }, { name: "Social Post", prompt: "Create social media posts for this launch:\n{{previous_output}}" }, ] }, ] export default function WorkflowsPage() { const user = useUser() const [steps, setSteps] = useState([]) const [workflowName, setWorkflowName] = useState("") const [workflowDescription, setWorkflowDescription] = useState("") const [isRunning, setIsRunning] = useState(false) const [isSaving, setIsSaving] = useState(false) const [savedSlug, setSavedSlug] = useState(null) const [currentStepIndex, setCurrentStepIndex] = useState(-1) const [variables, setVariables] = useState>({}) const [copied, setCopied] = useState(false) const [visibility, setVisibility] = useState<"private" | "public">("private") const [selectedModel, setSelectedModel] = useState(DEFAULT_MODEL) // Add a new step const addStep = () => { const newStep: WorkflowStep = { id: `step-${Date.now()}`, name: `Step ${steps.length + 1}`, prompt: "", iterations: 1, } setSteps([...steps, newStep]) } // Remove a step const removeStep = (id: string) => { setSteps(steps.filter(s => s.id !== id)) } // Update step const updateStep = (id: string, updates: Partial) => { setSteps(steps.map(s => s.id === id ? { ...s, ...updates } : s)) } // Move step up/down const moveStep = (index: number, direction: "up" | "down") => { const newIndex = direction === "up" ? index - 1 : index + 1 if (newIndex < 0 || newIndex >= steps.length) return const newSteps = [...steps] ;[newSteps[index], newSteps[newIndex]] = [newSteps[newIndex], newSteps[index]] setSteps(newSteps) } // Load template const loadTemplate = (templateIndex: number) => { const template = WORKFLOW_TEMPLATES[templateIndex] setWorkflowName(template.name) setSteps(template.steps.map((step, i) => ({ id: `step-${Date.now()}-${i}`, name: step.name, prompt: step.prompt, }))) // Extract variables from prompts const allPrompts = template.steps.map(s => s.prompt).join(" ") const varMatches = allPrompts.match(/\{\{(\w+)\}\}/g) || [] const uniqueVars = [...new Set(varMatches.map(v => v.replace(/\{\{|\}\}/g, "")))] .filter(v => v !== "previous_output") const newVars: Record = {} uniqueVars.forEach(v => { newVars[v] = "" }) setVariables(newVars) } // Run the workflow const runWorkflow = async () => { if (steps.length === 0) return setIsRunning(true) let previousOutput = "" for (let i = 0; i < steps.length; i++) { const iterations = steps[i].iterations || 1 for (let iter = 0; iter < iterations; iter++) { setCurrentStepIndex(i) setSteps(prev => prev.map((s, idx) => idx === i ? { ...s, isLoading: true, output: iterations > 1 ? `[Iteration ${iter + 1}/${iterations}]\n` : "" } : s )) // Replace variables in prompt let processedPrompt = steps[i].prompt Object.entries(variables).forEach(([key, value]) => { processedPrompt = processedPrompt.replace(new RegExp(`\\{\\{${key}\\}\\}`, "g"), value) }) processedPrompt = processedPrompt.replace(/\{\{previous_output\}\}/g, previousOutput) processedPrompt = processedPrompt.replace(/\{\{iteration\}\}/g, String(iter + 1)) try { const response = await fetch("/api/run", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ prompt: processedPrompt, model: selectedModel, }), }) const reader = response.body?.getReader() const decoder = new TextDecoder() let fullOutput = iterations > 1 ? `[Iteration ${iter + 1}/${iterations}]\n` : "" if (reader) { while (true) { const { done, value } = await reader.read() if (done) break const chunk = decoder.decode(value) fullOutput += chunk setSteps(prev => prev.map((s, idx) => idx === i ? { ...s, output: fullOutput } : s )) } } previousOutput = fullOutput setSteps(prev => prev.map((s, idx) => idx === i ? { ...s, isLoading: false } : s )) } catch (error) { setSteps(prev => prev.map((s, idx) => idx === i ? { ...s, isLoading: false, output: "Error: Failed to run step" } : s )) break } } } setIsRunning(false) setCurrentStepIndex(-1) } // Save workflow to database const saveWorkflow = async () => { if (!workflowName.trim() || steps.length === 0) return setIsSaving(true) try { const response = await fetch("/api/workflows", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ name: workflowName, description: workflowDescription, steps: steps.map(s => ({ name: s.name, prompt: s.prompt })), variables: Object.keys(variables), visibility, creatorId: user?.id, }), }) if (response.ok) { const data = await response.json() setSavedSlug(data.slug) } } catch (error) { console.error("Failed to save workflow:", error) } finally { setIsSaving(false) } } // Copy all outputs const copyAllOutputs = async () => { const allOutputs = steps .filter(s => s.output) .map(s => `## ${s.name}\n\n${s.output}`) .join("\n\n---\n\n") await navigator.clipboard.writeText(allOutputs) setCopied(true) setTimeout(() => setCopied(false), 2000) } return (
{/* Hero Section */}

AI Workflows

Chain multiple prompts together. Output from one step becomes input to the next.

{/* Templates */}

Quick Start Templates

{WORKFLOW_TEMPLATES.map((template, index) => ( loadTemplate(index)} >

{template.name}

{template.description}

{template.steps.length} steps
))}
{/* Workflow Builder */}
Workflow Builder Create your prompt chain
{/* Workflow Name & Description */}
setWorkflowName(e.target.value)} placeholder="My Custom Workflow" />
setWorkflowDescription(e.target.value)} placeholder="What does this workflow do?" />
{/* Steps */} {steps.length === 0 ? (

No steps yet. Add a step or select a template to get started.

) : (
{steps.map((step, index) => (
{index + 1} updateStep(step.id, { name: e.target.value })} className="font-medium w-48" /> {step.isLoading && ( )}