Spaces:
Configuration error
Configuration error
| "use client" | |
| import { useState } from "react" | |
| import { useUser } from "@stackframe/stack" | |
| import { useRouter } from "next/navigation" | |
| import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card" | |
| import { Button } from "@/components/ui/button" | |
| import { Badge } from "@/components/ui/badge" | |
| import { Textarea } from "@/components/ui/textarea" | |
| import { Input } from "@/components/ui/input" | |
| import { Label } from "@/components/ui/label" | |
| import { ModelSelector } from "@/components/model-selector" | |
| import { DEFAULT_MODEL } from "@/types/prompt" | |
| import ReactMarkdown from "react-markdown" | |
| import { | |
| BookOpen, | |
| Sparkles, | |
| Copy, | |
| Check, | |
| ChevronRight, | |
| Lightbulb, | |
| Play, | |
| Loader2, | |
| Save | |
| } from "lucide-react" | |
| interface Framework { | |
| id: string | |
| name: string | |
| acronym: string | |
| description: string | |
| color: string | |
| sections: { | |
| letter: string | |
| name: string | |
| description: string | |
| placeholder: string | |
| }[] | |
| example: string | |
| } | |
| const FRAMEWORKS: Framework[] = [ | |
| { | |
| id: "race", | |
| name: "RACE", | |
| acronym: "Role, Action, Context, Expectation", | |
| description: "Define who the AI is, what to do, the situation, and desired outcome", | |
| color: "from-blue-500 to-cyan-500", | |
| sections: [ | |
| { letter: "R", name: "Role", description: "Who should the AI act as?", placeholder: "e.g., Act as an experienced marketing consultant" }, | |
| { letter: "A", name: "Action", description: "What should the AI do?", placeholder: "e.g., Create a marketing strategy for..." }, | |
| { letter: "C", name: "Context", description: "What's the situation?", placeholder: "e.g., For a startup launching a new mobile app" }, | |
| { letter: "E", name: "Expectation", description: "What's the desired outcome?", placeholder: "e.g., Provide 5 actionable strategies with timelines" }, | |
| ], | |
| example: "Act as an experienced marketing consultant. Create a comprehensive marketing strategy for a B2B SaaS startup launching a project management tool. The target audience is small to medium businesses. Provide 5 actionable strategies with detailed implementation timelines and expected ROI." | |
| }, | |
| { | |
| id: "care", | |
| name: "CARE", | |
| acronym: "Context, Action, Result, Example", | |
| description: "Provide background, specify task, define output, and show examples", | |
| color: "from-green-500 to-emerald-500", | |
| sections: [ | |
| { letter: "C", name: "Context", description: "What's the background?", placeholder: "e.g., I'm building an e-commerce website" }, | |
| { letter: "A", name: "Action", description: "What task needs to be done?", placeholder: "e.g., Write product descriptions" }, | |
| { letter: "R", name: "Result", description: "What output do you want?", placeholder: "e.g., 100-word descriptions with features and benefits" }, | |
| { letter: "E", name: "Example", description: "Show an example if possible", placeholder: "e.g., Here's a sample description format..." }, | |
| ], | |
| example: "Context: I'm launching a premium coffee subscription service targeting busy professionals. Action: Write compelling email subject lines for our welcome sequence. Result: Give me 10 subject lines under 50 characters that drive high open rates. Example: 'Your morning ritual just upgraded' is the tone I'm looking for." | |
| }, | |
| { | |
| id: "ape", | |
| name: "APE", | |
| acronym: "Action, Purpose, Expectation", | |
| description: "Simple framework focusing on task, reason, and outcome", | |
| color: "from-orange-500 to-red-500", | |
| sections: [ | |
| { letter: "A", name: "Action", description: "What action should be taken?", placeholder: "e.g., Analyze this sales data" }, | |
| { letter: "P", name: "Purpose", description: "Why is this being done?", placeholder: "e.g., To identify top-performing products" }, | |
| { letter: "E", name: "Expectation", description: "What's the expected output?", placeholder: "e.g., A ranked list with insights" }, | |
| ], | |
| example: "Analyze the quarterly sales report I'll provide. The purpose is to identify our top 3 performing products and understand why they're successful. I expect a summary with percentage growth, key success factors, and recommendations for other products." | |
| }, | |
| { | |
| id: "create", | |
| name: "CREATE", | |
| acronym: "Character, Request, Examples, Adjustments, Type, Extras", | |
| description: "Comprehensive framework for detailed, nuanced prompts", | |
| color: "from-purple-500 to-pink-500", | |
| sections: [ | |
| { letter: "C", name: "Character", description: "Who is the AI?", placeholder: "e.g., You are a senior UX designer" }, | |
| { letter: "R", name: "Request", description: "What's the main request?", placeholder: "e.g., Review this app's user flow" }, | |
| { letter: "E", name: "Examples", description: "Provide examples", placeholder: "e.g., Good flows: Spotify, Bad flows: ..." }, | |
| { letter: "A", name: "Adjustments", description: "Any constraints?", placeholder: "e.g., Focus on mobile, ignore web" }, | |
| { letter: "T", name: "Type", description: "Output format?", placeholder: "e.g., Bullet points with priority levels" }, | |
| { letter: "E", name: "Extras", description: "Additional notes?", placeholder: "e.g., Be critical, I need honest feedback" }, | |
| ], | |
| example: "Character: You are a senior UX designer with 10+ years experience in mobile apps. Request: Review my fitness app's onboarding flow. Examples: Reference the smooth onboarding in Headspace and Duolingo. Adjustments: Focus only on the first-time user experience. Type: Provide feedback as numbered points with severity ratings (1-5). Extras: Be brutally honest - I need to ship next week." | |
| }, | |
| { | |
| id: "risen", | |
| name: "RISEN", | |
| acronym: "Role, Instructions, Steps, End goal, Narrowing", | |
| description: "Step-by-step framework with clear constraints", | |
| color: "from-indigo-500 to-violet-500", | |
| sections: [ | |
| { letter: "R", name: "Role", description: "Define the AI's role", placeholder: "e.g., Technical writer" }, | |
| { letter: "I", name: "Instructions", description: "Core instructions", placeholder: "e.g., Document this API" }, | |
| { letter: "S", name: "Steps", description: "Step-by-step process", placeholder: "e.g., 1. Overview 2. Endpoints 3. Examples" }, | |
| { letter: "E", name: "End goal", description: "Final objective", placeholder: "e.g., Complete API documentation" }, | |
| { letter: "N", name: "Narrowing", description: "Constraints & limits", placeholder: "e.g., Max 2 pages, no jargon" }, | |
| ], | |
| example: "Role: You are a technical writer specializing in developer documentation. Instructions: Create documentation for our REST API. Steps: Start with a quick overview, then list each endpoint with method/URL/params, add code examples, and end with error codes. End goal: A developer should be able to integrate within 30 minutes. Narrowing: Keep it under 3 pages, use simple language, and include curl examples only." | |
| }, | |
| ] | |
| export default function FrameworksClient() { | |
| const user = useUser() | |
| const router = useRouter() | |
| const [selectedFramework, setSelectedFramework] = useState<Framework | null>(null) | |
| const [sectionValues, setSectionValues] = useState<Record<string, string>>({}) | |
| const [generatedPrompt, setGeneratedPrompt] = useState("") | |
| const [copied, setCopied] = useState(false) | |
| // AI Testing state | |
| const [selectedModel, setSelectedModel] = useState<string>(DEFAULT_MODEL) | |
| const [aiOutput, setAiOutput] = useState("") | |
| const [isRunning, setIsRunning] = useState(false) | |
| // Save state | |
| const [isSaving, setIsSaving] = useState(false) | |
| const [promptTitle, setPromptTitle] = useState("") | |
| // Select a framework | |
| const selectFramework = (framework: Framework) => { | |
| setSelectedFramework(framework) | |
| setSectionValues({}) | |
| setGeneratedPrompt("") | |
| } | |
| // Update section value | |
| const updateSection = (letter: string, value: string) => { | |
| setSectionValues(prev => ({ ...prev, [letter]: value })) | |
| } | |
| // Generate prompt from sections | |
| const generatePrompt = () => { | |
| if (!selectedFramework) return | |
| const parts = selectedFramework.sections | |
| .map(section => { | |
| const value = sectionValues[section.letter] | |
| if (value?.trim()) { | |
| return `**${section.name}:** ${value}` | |
| } | |
| return null | |
| }) | |
| .filter(Boolean) | |
| setGeneratedPrompt(parts.join("\n\n")) | |
| } | |
| // Copy prompt | |
| const copyPrompt = async () => { | |
| await navigator.clipboard.writeText(generatedPrompt) | |
| setCopied(true) | |
| setTimeout(() => setCopied(false), 2000) | |
| } | |
| // Use example | |
| const useExample = () => { | |
| if (!selectedFramework) return | |
| setGeneratedPrompt(selectedFramework.example) | |
| } | |
| // Run with AI | |
| const runWithAI = async () => { | |
| if (!generatedPrompt.trim()) return | |
| setIsRunning(true) | |
| setAiOutput("") | |
| try { | |
| const response = await fetch("/api/run", { | |
| method: "POST", | |
| headers: { "Content-Type": "application/json" }, | |
| body: JSON.stringify({ | |
| prompt: generatedPrompt, | |
| model: selectedModel, | |
| }), | |
| }) | |
| const reader = response.body?.getReader() | |
| const decoder = new TextDecoder() | |
| if (reader) { | |
| while (true) { | |
| const { done, value } = await reader.read() | |
| if (done) break | |
| const chunk = decoder.decode(value) | |
| setAiOutput(prev => prev + chunk) | |
| } | |
| } | |
| } catch (error) { | |
| console.error("Failed to run:", error) | |
| setAiOutput("Error: Failed to get AI response") | |
| } finally { | |
| setIsRunning(false) | |
| } | |
| } | |
| // Save to Library | |
| const saveToLibrary = async () => { | |
| if (!generatedPrompt.trim() || !selectedFramework) return | |
| setIsSaving(true) | |
| try { | |
| const response = await fetch("/api/prompts", { | |
| method: "POST", | |
| headers: { "Content-Type": "application/json" }, | |
| body: JSON.stringify({ | |
| title: promptTitle || `${selectedFramework.name} Framework Prompt`, | |
| prompt: generatedPrompt, | |
| description: `Created using ${selectedFramework.name} framework`, | |
| category: "general", | |
| visibility: "private", | |
| creatorId: undefined, // handled server-side | |
| }), | |
| }) | |
| if (response.ok) { | |
| const data = await response.json() | |
| router.push(`/p/${data.slug}`) | |
| } | |
| } catch (error) { | |
| console.error("Failed to save:", error) | |
| } finally { | |
| setIsSaving(false) | |
| } | |
| } | |
| return ( | |
| <div className="container mx-auto px-4 py-8 max-w-6xl"> | |
| {/* Hero Section */} | |
| <div className="text-center mb-12"> | |
| <div className="inline-flex items-center justify-center h-16 w-16 rounded-2xl bg-linear-to-br from-primary to-accent mb-4"> | |
| <BookOpen className="h-8 w-8 text-white" /> | |
| </div> | |
| <h1 className="text-4xl md:text-5xl font-serif font-medium mb-4"> | |
| Prompt <span className="text-gradient italic">Frameworks</span> | |
| </h1> | |
| <p className="text-xl text-muted-foreground max-w-2xl mx-auto"> | |
| Use proven frameworks to create better prompts. Guided templates for consistent results. | |
| </p> | |
| </div> | |
| <div className="grid lg:grid-cols-3 gap-6"> | |
| {/* Framework Selector */} | |
| <div className="space-y-4"> | |
| <h2 className="text-lg font-semibold mb-4">Choose a Framework</h2> | |
| {FRAMEWORKS.map(framework => ( | |
| <Card | |
| key={framework.id} | |
| className={`cursor-pointer transition-all ${selectedFramework?.id === framework.id | |
| ? "ring-2 ring-primary" | |
| : "hover:border-primary/50" | |
| }`} | |
| onClick={() => selectFramework(framework)} | |
| > | |
| <CardContent className="p-4"> | |
| <div className="flex items-center justify-between mb-2"> | |
| <Badge className={`bg-linear-to-r ${framework.color} text-white border-0`}> | |
| {framework.name} | |
| </Badge> | |
| <ChevronRight className={`h-4 w-4 transition-transform ${selectedFramework?.id === framework.id ? "rotate-90" : "" | |
| }`} /> | |
| </div> | |
| <p className="text-sm font-medium mb-1">{framework.acronym}</p> | |
| <p className="text-sm text-muted-foreground">{framework.description}</p> | |
| </CardContent> | |
| </Card> | |
| ))} | |
| </div> | |
| {/* Framework Builder */} | |
| <div className="lg:col-span-2"> | |
| {selectedFramework ? ( | |
| <Card> | |
| <CardHeader> | |
| <div className="flex items-center justify-between"> | |
| <div> | |
| <CardTitle className="flex items-center gap-2"> | |
| <Badge className={`bg-linear-to-r ${selectedFramework.color} text-white border-0`}> | |
| {selectedFramework.name} | |
| </Badge> | |
| Framework Builder | |
| </CardTitle> | |
| <CardDescription>{selectedFramework.acronym}</CardDescription> | |
| </div> | |
| <Button variant="outline" size="sm" onClick={useExample} className="gap-2"> | |
| <Lightbulb className="h-4 w-4" /> | |
| See Example | |
| </Button> | |
| </div> | |
| </CardHeader> | |
| <CardContent className="space-y-6"> | |
| {/* Section Inputs */} | |
| <div className="space-y-4"> | |
| {selectedFramework.sections.map(section => ( | |
| <div key={section.letter} className="space-y-2"> | |
| <Label className="flex items-center gap-2"> | |
| <span className={`inline-flex items-center justify-center h-6 w-6 rounded-full bg-linear-to-r ${selectedFramework.color} text-white text-xs font-bold`}> | |
| {section.letter} | |
| </span> | |
| {section.name} | |
| <span className="text-muted-foreground font-normal"> | |
| - {section.description} | |
| </span> | |
| </Label> | |
| <Textarea | |
| value={sectionValues[section.letter] || ""} | |
| onChange={(e) => updateSection(section.letter, e.target.value)} | |
| placeholder={section.placeholder} | |
| rows={2} | |
| /> | |
| </div> | |
| ))} | |
| </div> | |
| {/* Generate Button */} | |
| <Button onClick={generatePrompt} className="w-full gap-2"> | |
| <Sparkles className="h-4 w-4" /> | |
| Generate Prompt | |
| </Button> | |
| {/* Generated Prompt */} | |
| {generatedPrompt && ( | |
| <div className="space-y-4"> | |
| <div className="flex items-center justify-between"> | |
| <Label>Generated Prompt</Label> | |
| <Button variant="ghost" size="sm" onClick={copyPrompt} className="gap-2"> | |
| {copied ? ( | |
| <> | |
| <Check className="h-4 w-4" /> | |
| Copied! | |
| </> | |
| ) : ( | |
| <> | |
| <Copy className="h-4 w-4" /> | |
| Copy | |
| </> | |
| )} | |
| </Button> | |
| </div> | |
| <div className="p-4 rounded-lg border bg-muted/30 whitespace-pre-wrap"> | |
| {generatedPrompt} | |
| </div> | |
| {/* Run with AI Section */} | |
| <div className="border-t pt-4 space-y-3"> | |
| <Label>Test with AI</Label> | |
| <div className="flex gap-3"> | |
| <div className="flex-1"> | |
| <ModelSelector | |
| value={selectedModel} | |
| onChange={setSelectedModel} | |
| /> | |
| </div> | |
| <Button | |
| onClick={runWithAI} | |
| disabled={isRunning} | |
| className="gap-2" | |
| > | |
| {isRunning ? ( | |
| <> | |
| <Loader2 className="h-4 w-4 animate-spin" /> | |
| Running... | |
| </> | |
| ) : ( | |
| <> | |
| <Play className="h-4 w-4" /> | |
| Run with AI | |
| </> | |
| )} | |
| </Button> | |
| </div> | |
| {/* AI Output */} | |
| {aiOutput && ( | |
| <div className="max-h-60 overflow-auto rounded-lg border bg-background p-4"> | |
| <div className="prose prose-sm dark:prose-invert max-w-none"> | |
| <ReactMarkdown>{aiOutput}</ReactMarkdown> | |
| </div> | |
| </div> | |
| )} | |
| </div> | |
| {/* Save to Library Section */} | |
| <div className="border-t pt-4 space-y-3"> | |
| <Label>Save to Library</Label> | |
| <div className="flex gap-3"> | |
| <Input | |
| value={promptTitle} | |
| onChange={(e) => setPromptTitle(e.target.value)} | |
| placeholder="Prompt title (optional)" | |
| className="flex-1" | |
| /> | |
| <Button | |
| variant="outline" | |
| onClick={saveToLibrary} | |
| disabled={isSaving} | |
| className="gap-2" | |
| > | |
| {isSaving ? ( | |
| <> | |
| <Loader2 className="h-4 w-4 animate-spin" /> | |
| Saving... | |
| </> | |
| ) : ( | |
| <> | |
| <Save className="h-4 w-4" /> | |
| Save | |
| </> | |
| )} | |
| </Button> | |
| </div> | |
| </div> | |
| </div> | |
| )} | |
| </CardContent> | |
| </Card> | |
| ) : ( | |
| <Card className="h-full flex items-center justify-center border-dashed"> | |
| <CardContent className="p-12 text-center text-muted-foreground"> | |
| <BookOpen className="h-16 w-16 mx-auto mb-4 opacity-20" /> | |
| <p className="text-lg font-medium mb-2">Select a Framework</p> | |
| <p>Choose a framework from the left to start building your prompt</p> | |
| </CardContent> | |
| </Card> | |
| )} | |
| </div> | |
| </div> | |
| </div> | |
| ) | |
| } | |