feat: Add AI Code Generation v2 with system templates and snippets
- Create comprehensive system templates (inventory, quests, currency, combat, friends) - Add platform-specific code patterns and snippets for Roblox/UEFN/Spatial - Build AIGenerationPanel component with template browser and parameter editor - Add configurable generation with sliders, switches, and dropdowns - Include code snippets library with copy/insert functionality - Integrate AI Generate button into Toolbar (desktop and mobile) - Support custom AI generation prompts (placeholder for future AI integration)
This commit is contained in:
parent
159e40f02c
commit
9c54fb3386
6 changed files with 2687 additions and 2 deletions
16
src/App.tsx
16
src/App.tsx
|
|
@ -36,6 +36,7 @@ const AvatarToolkit = lazy(() => import('./components/AvatarToolkit'));
|
|||
const VisualScriptingCanvas = lazy(() => import('./components/visual-scripting/VisualScriptingCanvas'));
|
||||
const AssetLibrary = lazy(() => import('./components/assets/AssetLibrary'));
|
||||
const LivePreview = lazy(() => import('./components/preview/LivePreview'));
|
||||
const AIGenerationPanel = lazy(() => import('./components/ai-generation/AIGenerationPanel'));
|
||||
|
||||
function App() {
|
||||
const [currentCode, setCurrentCode] = useState('');
|
||||
|
|
@ -50,6 +51,7 @@ function App() {
|
|||
const [showVisualScripting, setShowVisualScripting] = useState(false);
|
||||
const [showAssetLibrary, setShowAssetLibrary] = useState(false);
|
||||
const [showLivePreview, setShowLivePreview] = useState(false);
|
||||
const [showAIGeneration, setShowAIGeneration] = useState(false);
|
||||
const [code, setCode] = useState('');
|
||||
const [currentPlatform, setCurrentPlatform] = useState<PlatformId>('roblox');
|
||||
const isMobile = useIsMobile();
|
||||
|
|
@ -489,6 +491,7 @@ end)`,
|
|||
onVisualScriptingClick={() => setShowVisualScripting(true)}
|
||||
onAssetLibraryClick={() => setShowAssetLibrary(true)}
|
||||
onLivePreviewClick={() => setShowLivePreview(true)}
|
||||
onAIGenerationClick={() => setShowAIGeneration(true)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
|
@ -654,6 +657,19 @@ end)`,
|
|||
</Dialog>
|
||||
)}
|
||||
</Suspense>
|
||||
<Suspense fallback={null}>
|
||||
{showAIGeneration && (
|
||||
<AIGenerationPanel
|
||||
isOpen={showAIGeneration}
|
||||
onClose={() => setShowAIGeneration(false)}
|
||||
currentPlatform={currentPlatform}
|
||||
onCodeGenerated={(code) => {
|
||||
setCurrentCode(code);
|
||||
handleCodeChange(code);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Suspense>
|
||||
<Suspense fallback={null}>
|
||||
<WelcomeDialog />
|
||||
</Suspense>
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import {
|
|||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from '@/components/ui/dropdown-menu';
|
||||
import { Copy, FileCode, Download, Info, Play, FolderPlus, User, SignOut, List, ArrowsLeftRight, UserCircle, GitBranch, Package, Cube } from '@phosphor-icons/react';
|
||||
import { Copy, FileCode, Download, Info, Play, FolderPlus, User, SignOut, List, ArrowsLeftRight, UserCircle, GitBranch, Package, Cube, MagicWand } from '@phosphor-icons/react';
|
||||
import { toast } from 'sonner';
|
||||
import { useState, useEffect, useCallback, memo } from 'react';
|
||||
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from './ui/dialog';
|
||||
|
|
@ -28,9 +28,10 @@ interface ToolbarProps {
|
|||
onVisualScriptingClick?: () => void;
|
||||
onAssetLibraryClick?: () => void;
|
||||
onLivePreviewClick?: () => void;
|
||||
onAIGenerationClick?: () => void;
|
||||
}
|
||||
|
||||
export function Toolbar({ code, onTemplatesClick, onPreviewClick, onNewProjectClick, currentPlatform, onPlatformChange, onTranslateClick, onAvatarToolkitClick, onVisualScriptingClick, onAssetLibraryClick, onLivePreviewClick }: ToolbarProps) {
|
||||
export function Toolbar({ code, onTemplatesClick, onPreviewClick, onNewProjectClick, currentPlatform, onPlatformChange, onTranslateClick, onAvatarToolkitClick, onVisualScriptingClick, onAssetLibraryClick, onLivePreviewClick, onAIGenerationClick }: ToolbarProps) {
|
||||
const [showInfo, setShowInfo] = useState(false);
|
||||
const [user, setUser] = useState<{ login: string; avatarUrl: string; email: string } | null>(null);
|
||||
|
||||
|
|
@ -178,6 +179,25 @@ export function Toolbar({ code, onTemplatesClick, onPreviewClick, onNewProjectCl
|
|||
</Tooltip>
|
||||
)}
|
||||
|
||||
{/* AI Generation Button */}
|
||||
{onAIGenerationClick && (
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={onAIGenerationClick}
|
||||
className="h-8 px-3 text-xs gap-1 bg-accent/10 border-accent/30 hover:bg-accent/20"
|
||||
aria-label="AI Code Generator"
|
||||
>
|
||||
<MagicWand size={14} />
|
||||
<span>AI Generate</span>
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>AI-Powered Code & System Generation</TooltipContent>
|
||||
</Tooltip>
|
||||
)}
|
||||
|
||||
<div className="h-6 w-px bg-border mx-1" />
|
||||
|
||||
<Tooltip>
|
||||
|
|
@ -316,6 +336,12 @@ export function Toolbar({ code, onTemplatesClick, onPreviewClick, onNewProjectCl
|
|||
<span>3D Preview</span>
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
{onAIGenerationClick && (
|
||||
<DropdownMenuItem onClick={onAIGenerationClick}>
|
||||
<MagicWand className="mr-2" size={16} />
|
||||
<span>AI Generate</span>
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
<DropdownMenuItem onClick={handleCopy}>
|
||||
<Copy className="mr-2" size={16} />
|
||||
<span>Copy Code</span>
|
||||
|
|
|
|||
560
src/components/ai-generation/AIGenerationPanel.tsx
Normal file
560
src/components/ai-generation/AIGenerationPanel.tsx
Normal file
|
|
@ -0,0 +1,560 @@
|
|||
'use client';
|
||||
|
||||
import { useState, useMemo } from 'react';
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog';
|
||||
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 { Badge } from '@/components/ui/badge';
|
||||
import { ScrollArea } from '@/components/ui/scroll-area';
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
||||
import { Switch } from '@/components/ui/switch';
|
||||
import { Slider } from '@/components/ui/slider';
|
||||
import { Separator } from '@/components/ui/separator';
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '@/components/ui/select';
|
||||
import {
|
||||
Accordion,
|
||||
AccordionContent,
|
||||
AccordionItem,
|
||||
AccordionTrigger,
|
||||
} from '@/components/ui/accordion';
|
||||
import {
|
||||
Wand2,
|
||||
Copy,
|
||||
Check,
|
||||
Sparkles,
|
||||
Code,
|
||||
Search,
|
||||
Layers,
|
||||
Gamepad2,
|
||||
Coins,
|
||||
Swords,
|
||||
Users,
|
||||
Layout,
|
||||
Globe,
|
||||
Network,
|
||||
CreditCard,
|
||||
ChevronRight,
|
||||
FileCode,
|
||||
Zap,
|
||||
} from 'lucide-react';
|
||||
import { toast } from 'sonner';
|
||||
|
||||
import {
|
||||
SYSTEM_TEMPLATES,
|
||||
SYSTEM_CATEGORIES,
|
||||
SystemCategory,
|
||||
SystemTemplate,
|
||||
TemplateParameter,
|
||||
generateCode,
|
||||
getTemplatesByCategory,
|
||||
getTemplatesForPlatform,
|
||||
} from '@/lib/ai-generation/system-templates';
|
||||
import {
|
||||
CODE_SNIPPETS,
|
||||
getSnippetsForPlatform,
|
||||
getPatternsForPlatform,
|
||||
} from '@/lib/ai-generation/generation-prompts';
|
||||
import { PlatformId } from '@/lib/platforms';
|
||||
|
||||
// Category icons
|
||||
const CATEGORY_ICONS: Record<SystemCategory, React.ReactNode> = {
|
||||
gameplay: <Gamepad2 className="h-4 w-4" />,
|
||||
economy: <Coins className="h-4 w-4" />,
|
||||
combat: <Swords className="h-4 w-4" />,
|
||||
social: <Users className="h-4 w-4" />,
|
||||
ui: <Layout className="h-4 w-4" />,
|
||||
world: <Globe className="h-4 w-4" />,
|
||||
multiplayer: <Network className="h-4 w-4" />,
|
||||
monetization: <CreditCard className="h-4 w-4" />,
|
||||
};
|
||||
|
||||
interface AIGenerationPanelProps {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
currentPlatform: PlatformId;
|
||||
onCodeGenerated: (code: string) => void;
|
||||
}
|
||||
|
||||
export default function AIGenerationPanel({
|
||||
isOpen,
|
||||
onClose,
|
||||
currentPlatform,
|
||||
onCodeGenerated,
|
||||
}: AIGenerationPanelProps) {
|
||||
const [activeTab, setActiveTab] = useState<'templates' | 'snippets' | 'custom'>('templates');
|
||||
const [selectedCategory, setSelectedCategory] = useState<SystemCategory | null>(null);
|
||||
const [selectedTemplate, setSelectedTemplate] = useState<SystemTemplate | null>(null);
|
||||
const [parameters, setParameters] = useState<Record<string, any>>({});
|
||||
const [generatedCode, setGeneratedCode] = useState('');
|
||||
const [copied, setCopied] = useState(false);
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
const [customPrompt, setCustomPrompt] = useState('');
|
||||
const [isGenerating, setIsGenerating] = useState(false);
|
||||
|
||||
// Map PlatformId to template platform
|
||||
const templatePlatform = useMemo(() => {
|
||||
switch (currentPlatform) {
|
||||
case 'roblox':
|
||||
return 'roblox';
|
||||
case 'uefn':
|
||||
return 'uefn';
|
||||
case 'spatial':
|
||||
return 'spatial';
|
||||
default:
|
||||
return 'roblox';
|
||||
}
|
||||
}, [currentPlatform]);
|
||||
|
||||
// Filter templates by platform and search
|
||||
const filteredTemplates = useMemo(() => {
|
||||
let templates = getTemplatesForPlatform(templatePlatform);
|
||||
|
||||
if (selectedCategory) {
|
||||
templates = templates.filter((t) => t.category === selectedCategory);
|
||||
}
|
||||
|
||||
if (searchQuery) {
|
||||
const query = searchQuery.toLowerCase();
|
||||
templates = templates.filter(
|
||||
(t) =>
|
||||
t.name.toLowerCase().includes(query) ||
|
||||
t.description.toLowerCase().includes(query)
|
||||
);
|
||||
}
|
||||
|
||||
return templates;
|
||||
}, [templatePlatform, selectedCategory, searchQuery]);
|
||||
|
||||
// Get snippets for current platform
|
||||
const snippets = useMemo(() => {
|
||||
return getSnippetsForPlatform(templatePlatform);
|
||||
}, [templatePlatform]);
|
||||
|
||||
// Initialize parameters when template is selected
|
||||
const handleTemplateSelect = (template: SystemTemplate) => {
|
||||
setSelectedTemplate(template);
|
||||
const initialParams: Record<string, any> = {};
|
||||
template.parameters.forEach((param) => {
|
||||
initialParams[param.id] = param.default;
|
||||
});
|
||||
setParameters(initialParams);
|
||||
setGeneratedCode('');
|
||||
};
|
||||
|
||||
// Generate code from template
|
||||
const handleGenerate = () => {
|
||||
if (!selectedTemplate) return;
|
||||
|
||||
const code = generateCode(selectedTemplate, templatePlatform, parameters);
|
||||
setGeneratedCode(code);
|
||||
toast.success('Code generated!');
|
||||
};
|
||||
|
||||
// Copy code to clipboard
|
||||
const handleCopy = async () => {
|
||||
await navigator.clipboard.writeText(generatedCode);
|
||||
setCopied(true);
|
||||
toast.success('Copied to clipboard!');
|
||||
setTimeout(() => setCopied(false), 2000);
|
||||
};
|
||||
|
||||
// Insert code into editor
|
||||
const handleInsert = () => {
|
||||
onCodeGenerated(generatedCode);
|
||||
toast.success('Code inserted into editor!');
|
||||
onClose();
|
||||
};
|
||||
|
||||
// Copy snippet
|
||||
const handleCopySnippet = async (code: string) => {
|
||||
await navigator.clipboard.writeText(code);
|
||||
toast.success('Snippet copied!');
|
||||
};
|
||||
|
||||
// Insert snippet
|
||||
const handleInsertSnippet = (code: string) => {
|
||||
onCodeGenerated(code);
|
||||
toast.success('Snippet inserted!');
|
||||
onClose();
|
||||
};
|
||||
|
||||
// Render parameter input
|
||||
const renderParameterInput = (param: TemplateParameter) => {
|
||||
const value = parameters[param.id];
|
||||
|
||||
switch (param.type) {
|
||||
case 'number':
|
||||
return (
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<Label className="text-xs">{param.name}</Label>
|
||||
<span className="text-xs text-muted-foreground">{value}</span>
|
||||
</div>
|
||||
<Slider
|
||||
value={[value]}
|
||||
onValueChange={([v]) =>
|
||||
setParameters((p) => ({ ...p, [param.id]: v }))
|
||||
}
|
||||
min={param.validation?.min || 0}
|
||||
max={param.validation?.max || 100}
|
||||
step={1}
|
||||
className="w-full"
|
||||
/>
|
||||
<p className="text-[10px] text-muted-foreground">{param.description}</p>
|
||||
</div>
|
||||
);
|
||||
|
||||
case 'boolean':
|
||||
return (
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<Label className="text-xs">{param.name}</Label>
|
||||
<p className="text-[10px] text-muted-foreground">{param.description}</p>
|
||||
</div>
|
||||
<Switch
|
||||
checked={value}
|
||||
onCheckedChange={(checked) =>
|
||||
setParameters((p) => ({ ...p, [param.id]: checked }))
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
case 'select':
|
||||
return (
|
||||
<div className="space-y-1">
|
||||
<Label className="text-xs">{param.name}</Label>
|
||||
<Select
|
||||
value={value}
|
||||
onValueChange={(v) => setParameters((p) => ({ ...p, [param.id]: v }))}
|
||||
>
|
||||
<SelectTrigger className="h-8 text-xs">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{param.options?.map((opt) => (
|
||||
<SelectItem key={opt.value} value={opt.value}>
|
||||
{opt.label}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<p className="text-[10px] text-muted-foreground">{param.description}</p>
|
||||
</div>
|
||||
);
|
||||
|
||||
case 'array':
|
||||
return (
|
||||
<div className="space-y-1">
|
||||
<Label className="text-xs">{param.name}</Label>
|
||||
<Input
|
||||
value={Array.isArray(value) ? value.join(', ') : value}
|
||||
onChange={(e) =>
|
||||
setParameters((p) => ({
|
||||
...p,
|
||||
[param.id]: e.target.value.split(',').map((s) => s.trim()),
|
||||
}))
|
||||
}
|
||||
className="h-8 text-xs"
|
||||
placeholder="item1, item2, item3"
|
||||
/>
|
||||
<p className="text-[10px] text-muted-foreground">{param.description}</p>
|
||||
</div>
|
||||
);
|
||||
|
||||
default:
|
||||
return (
|
||||
<div className="space-y-1">
|
||||
<Label className="text-xs">{param.name}</Label>
|
||||
<Input
|
||||
value={value}
|
||||
onChange={(e) =>
|
||||
setParameters((p) => ({ ...p, [param.id]: e.target.value }))
|
||||
}
|
||||
className="h-8 text-xs"
|
||||
/>
|
||||
<p className="text-[10px] text-muted-foreground">{param.description}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog open={isOpen} onOpenChange={onClose}>
|
||||
<DialogContent className="max-w-5xl max-h-[85vh] p-0 overflow-hidden">
|
||||
<DialogHeader className="px-6 py-4 border-b">
|
||||
<DialogTitle className="flex items-center gap-2">
|
||||
<Sparkles className="h-5 w-5 text-primary" />
|
||||
AI Code Generator
|
||||
<Badge variant="secondary" className="ml-2 text-xs">
|
||||
{templatePlatform.toUpperCase()}
|
||||
</Badge>
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="flex h-[70vh]">
|
||||
{/* Left Panel - Categories/Templates */}
|
||||
<div className="w-72 border-r flex flex-col">
|
||||
<Tabs value={activeTab} onValueChange={(v) => setActiveTab(v as any)} className="flex-1 flex flex-col">
|
||||
<TabsList className="grid grid-cols-3 mx-3 mt-3">
|
||||
<TabsTrigger value="templates" className="text-xs">
|
||||
<Layers className="h-3 w-3 mr-1" />
|
||||
Systems
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="snippets" className="text-xs">
|
||||
<Code className="h-3 w-3 mr-1" />
|
||||
Snippets
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="custom" className="text-xs">
|
||||
<Wand2 className="h-3 w-3 mr-1" />
|
||||
Custom
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
<div className="px-3 py-2">
|
||||
<div className="relative">
|
||||
<Search className="absolute left-2.5 top-2.5 h-3 w-3 text-muted-foreground" />
|
||||
<Input
|
||||
placeholder="Search..."
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
className="pl-8 h-8 text-xs"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ScrollArea className="flex-1">
|
||||
<TabsContent value="templates" className="mt-0 p-2">
|
||||
{/* Category filters */}
|
||||
<div className="flex flex-wrap gap-1 mb-3">
|
||||
<Badge
|
||||
variant={selectedCategory === null ? 'default' : 'outline'}
|
||||
className="cursor-pointer text-[10px]"
|
||||
onClick={() => setSelectedCategory(null)}
|
||||
>
|
||||
All
|
||||
</Badge>
|
||||
{(Object.keys(SYSTEM_CATEGORIES) as SystemCategory[]).map((cat) => (
|
||||
<Badge
|
||||
key={cat}
|
||||
variant={selectedCategory === cat ? 'default' : 'outline'}
|
||||
className="cursor-pointer text-[10px]"
|
||||
onClick={() => setSelectedCategory(cat)}
|
||||
>
|
||||
{CATEGORY_ICONS[cat]}
|
||||
<span className="ml-1">{SYSTEM_CATEGORIES[cat].label.split(' ')[0]}</span>
|
||||
</Badge>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Template list */}
|
||||
<div className="space-y-1">
|
||||
{filteredTemplates.map((template) => (
|
||||
<div
|
||||
key={template.id}
|
||||
className={`p-2 rounded-md border cursor-pointer transition-colors ${
|
||||
selectedTemplate?.id === template.id
|
||||
? 'bg-primary/10 border-primary'
|
||||
: 'hover:bg-accent'
|
||||
}`}
|
||||
onClick={() => handleTemplateSelect(template)}
|
||||
>
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="font-medium text-sm">{template.name}</span>
|
||||
<Badge
|
||||
variant="secondary"
|
||||
className="text-[10px]"
|
||||
style={{
|
||||
backgroundColor: `${SYSTEM_CATEGORIES[template.category].color}20`,
|
||||
color: SYSTEM_CATEGORIES[template.category].color,
|
||||
}}
|
||||
>
|
||||
{template.complexity}
|
||||
</Badge>
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground mt-1 line-clamp-2">
|
||||
{template.description}
|
||||
</p>
|
||||
<div className="flex items-center gap-2 mt-2 text-[10px] text-muted-foreground">
|
||||
<span>~{template.estimatedLines} lines</span>
|
||||
<span>•</span>
|
||||
<span>{template.features.length} features</span>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="snippets" className="mt-0 p-2">
|
||||
<Accordion type="multiple" className="w-full">
|
||||
{Object.entries(snippets).map(([name, code]) => (
|
||||
<AccordionItem key={name} value={name}>
|
||||
<AccordionTrigger className="text-sm py-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<FileCode className="h-4 w-4" />
|
||||
{name.replace(/-/g, ' ').replace(/\b\w/g, (c) => c.toUpperCase())}
|
||||
</div>
|
||||
</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
<div className="space-y-2">
|
||||
<pre className="text-[10px] bg-muted p-2 rounded overflow-x-auto max-h-32">
|
||||
{code.slice(0, 200)}...
|
||||
</pre>
|
||||
<div className="flex gap-1">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="h-7 text-xs flex-1"
|
||||
onClick={() => handleCopySnippet(code)}
|
||||
>
|
||||
<Copy className="h-3 w-3 mr-1" />
|
||||
Copy
|
||||
</Button>
|
||||
<Button
|
||||
variant="default"
|
||||
size="sm"
|
||||
className="h-7 text-xs flex-1"
|
||||
onClick={() => handleInsertSnippet(code)}
|
||||
>
|
||||
<Zap className="h-3 w-3 mr-1" />
|
||||
Insert
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
))}
|
||||
</Accordion>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="custom" className="mt-0 p-3">
|
||||
<div className="space-y-3">
|
||||
<Label className="text-xs">Describe what you want to create:</Label>
|
||||
<Textarea
|
||||
placeholder="E.g., Create a system where players can collect coins that spawn randomly around the map. Include a leaderboard that shows top collectors."
|
||||
value={customPrompt}
|
||||
onChange={(e) => setCustomPrompt(e.target.value)}
|
||||
className="min-h-[150px] text-xs"
|
||||
/>
|
||||
<Button
|
||||
className="w-full"
|
||||
disabled={!customPrompt.trim() || isGenerating}
|
||||
onClick={() => {
|
||||
// For now, just show a message
|
||||
toast.info('Custom AI generation coming soon! Use system templates for now.');
|
||||
}}
|
||||
>
|
||||
<Wand2 className="h-4 w-4 mr-2" />
|
||||
Generate with AI
|
||||
</Button>
|
||||
<p className="text-[10px] text-muted-foreground text-center">
|
||||
Tip: Be specific about mechanics, numbers, and behaviors you want.
|
||||
</p>
|
||||
</div>
|
||||
</TabsContent>
|
||||
</ScrollArea>
|
||||
</Tabs>
|
||||
</div>
|
||||
|
||||
{/* Right Panel - Configuration/Preview */}
|
||||
<div className="flex-1 flex flex-col">
|
||||
{selectedTemplate ? (
|
||||
<>
|
||||
{/* Template Info */}
|
||||
<div className="p-4 border-b">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<h3 className="font-semibold">{selectedTemplate.name}</h3>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{selectedTemplate.description}
|
||||
</p>
|
||||
</div>
|
||||
<Button onClick={handleGenerate}>
|
||||
<Sparkles className="h-4 w-4 mr-2" />
|
||||
Generate
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-wrap gap-1 mt-3">
|
||||
{selectedTemplate.features.map((feature) => (
|
||||
<Badge key={feature} variant="outline" className="text-[10px]">
|
||||
{feature}
|
||||
</Badge>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Parameters */}
|
||||
{selectedTemplate.parameters.length > 0 && (
|
||||
<div className="p-4 border-b">
|
||||
<h4 className="font-medium text-sm mb-3">Configuration</h4>
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
{selectedTemplate.parameters.map((param) => (
|
||||
<div key={param.id}>{renderParameterInput(param)}</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Generated Code */}
|
||||
<div className="flex-1 flex flex-col min-h-0">
|
||||
<div className="flex items-center justify-between px-4 py-2 border-b">
|
||||
<span className="font-medium text-sm">Generated Code</span>
|
||||
{generatedCode && (
|
||||
<div className="flex gap-1">
|
||||
<Button variant="outline" size="sm" onClick={handleCopy}>
|
||||
{copied ? (
|
||||
<Check className="h-3 w-3 mr-1" />
|
||||
) : (
|
||||
<Copy className="h-3 w-3 mr-1" />
|
||||
)}
|
||||
{copied ? 'Copied!' : 'Copy'}
|
||||
</Button>
|
||||
<Button size="sm" onClick={handleInsert}>
|
||||
<ChevronRight className="h-3 w-3 mr-1" />
|
||||
Insert
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<ScrollArea className="flex-1">
|
||||
{generatedCode ? (
|
||||
<pre className="p-4 text-xs font-mono whitespace-pre-wrap">
|
||||
{generatedCode}
|
||||
</pre>
|
||||
) : (
|
||||
<div className="flex items-center justify-center h-full text-muted-foreground">
|
||||
<div className="text-center">
|
||||
<Sparkles className="h-8 w-8 mx-auto mb-2 opacity-50" />
|
||||
<p className="text-sm">Configure parameters and click Generate</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</ScrollArea>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<div className="flex items-center justify-center h-full text-muted-foreground">
|
||||
<div className="text-center">
|
||||
<Layers className="h-12 w-12 mx-auto mb-4 opacity-50" />
|
||||
<h3 className="font-medium text-lg mb-2">Select a System Template</h3>
|
||||
<p className="text-sm max-w-xs">
|
||||
Choose from pre-built game systems or create custom code with AI assistance.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
1
src/components/ai-generation/index.ts
Normal file
1
src/components/ai-generation/index.ts
Normal file
|
|
@ -0,0 +1 @@
|
|||
export { default as AIGenerationPanel } from './AIGenerationPanel';
|
||||
433
src/lib/ai-generation/generation-prompts.ts
Normal file
433
src/lib/ai-generation/generation-prompts.ts
Normal file
|
|
@ -0,0 +1,433 @@
|
|||
/**
|
||||
* AI Code Generation v2 - Generation Prompts
|
||||
* Prompts and utilities for AI-assisted code generation
|
||||
*/
|
||||
|
||||
export interface GenerationPrompt {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
category: string;
|
||||
template: string;
|
||||
examples: string[];
|
||||
}
|
||||
|
||||
export interface GenerationResult {
|
||||
code: string;
|
||||
explanation: string;
|
||||
warnings: string[];
|
||||
suggestions: string[];
|
||||
}
|
||||
|
||||
// Platform-specific code patterns
|
||||
export const PLATFORM_PATTERNS = {
|
||||
roblox: {
|
||||
services: [
|
||||
'Players',
|
||||
'ReplicatedStorage',
|
||||
'ServerStorage',
|
||||
'DataStoreService',
|
||||
'TweenService',
|
||||
'RunService',
|
||||
'UserInputService',
|
||||
'Workspace',
|
||||
'Lighting',
|
||||
'SoundService',
|
||||
'MarketplaceService',
|
||||
'TeleportService',
|
||||
],
|
||||
commonPatterns: {
|
||||
playerJoin: `Players.PlayerAdded:Connect(function(player)
|
||||
-- Code here
|
||||
end)`,
|
||||
dataStore: `local DataStore = DataStoreService:GetDataStore("StoreName")
|
||||
local success, data = pcall(function()
|
||||
return DataStore:GetAsync(key)
|
||||
end)`,
|
||||
tween: `local tweenInfo = TweenInfo.new(duration, Enum.EasingStyle.Quad, Enum.EasingDirection.Out)
|
||||
local tween = TweenService:Create(object, tweenInfo, {Property = value})
|
||||
tween:Play()`,
|
||||
remoteEvent: `local RemoteEvent = ReplicatedStorage:WaitForChild("EventName")
|
||||
-- Server:
|
||||
RemoteEvent.OnServerEvent:Connect(function(player, ...)
|
||||
end)
|
||||
-- Client:
|
||||
RemoteEvent:FireServer(...)`,
|
||||
},
|
||||
},
|
||||
uefn: {
|
||||
modules: [
|
||||
'/Fortnite.com/Devices',
|
||||
'/Fortnite.com/Characters',
|
||||
'/Fortnite.com/Game',
|
||||
'/Verse.org/Simulation',
|
||||
'/Verse.org/Random',
|
||||
],
|
||||
commonPatterns: {
|
||||
device: `device_name := class(creative_device):
|
||||
OnBegin<override>()<suspends> : void =
|
||||
# Code here`,
|
||||
subscription: `if (Agent := agent[Player]):
|
||||
Agent.JumpedEvent.Subscribe(OnPlayerJumped)`,
|
||||
async: `spawn:
|
||||
# Async code here
|
||||
Sleep(1.0)`,
|
||||
},
|
||||
},
|
||||
spatial: {
|
||||
modules: ['spatial', 'three', 'physics', 'networking'],
|
||||
commonPatterns: {
|
||||
component: `export class MyComponent extends Component {
|
||||
onStart(): void {
|
||||
// Code here
|
||||
}
|
||||
}`,
|
||||
networked: `@networked
|
||||
class NetworkedState {
|
||||
@networked health: number = 100;
|
||||
}`,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
// Generation prompt templates
|
||||
export const GENERATION_PROMPTS: GenerationPrompt[] = [
|
||||
{
|
||||
id: 'game-mechanic',
|
||||
name: 'Game Mechanic',
|
||||
description: 'Generate a custom game mechanic',
|
||||
category: 'gameplay',
|
||||
template: `Generate a {{platform}} script for the following game mechanic:
|
||||
|
||||
**Mechanic Description:**
|
||||
{{description}}
|
||||
|
||||
**Requirements:**
|
||||
- Clean, well-commented code
|
||||
- Error handling where appropriate
|
||||
- Modular design for easy modification
|
||||
- Performance optimized
|
||||
|
||||
{{additionalRequirements}}`,
|
||||
examples: [
|
||||
'Double jump ability with cooldown',
|
||||
'Grappling hook that pulls player to surfaces',
|
||||
'Time-slowing ability when in danger',
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'npc-behavior',
|
||||
name: 'NPC Behavior',
|
||||
description: 'Generate NPC AI behavior',
|
||||
category: 'ai',
|
||||
template: `Generate a {{platform}} NPC behavior script:
|
||||
|
||||
**NPC Type:** {{npcType}}
|
||||
**Behavior:** {{behavior}}
|
||||
|
||||
Requirements:
|
||||
- State machine pattern
|
||||
- Patrol, chase, and return states
|
||||
- Detection range: {{detectionRange}} studs
|
||||
- Attack range: {{attackRange}} studs
|
||||
|
||||
{{additionalRequirements}}`,
|
||||
examples: [
|
||||
'Guard that patrols and chases intruders',
|
||||
'Shopkeeper that interacts with players',
|
||||
'Boss with multiple attack phases',
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'ui-system',
|
||||
name: 'UI System',
|
||||
description: 'Generate UI components and systems',
|
||||
category: 'ui',
|
||||
template: `Generate a {{platform}} UI system:
|
||||
|
||||
**UI Type:** {{uiType}}
|
||||
**Features:** {{features}}
|
||||
|
||||
Requirements:
|
||||
- Responsive design
|
||||
- Smooth animations
|
||||
- Accessibility considerations
|
||||
- Mobile support
|
||||
|
||||
{{additionalRequirements}}`,
|
||||
examples: ['Health bar with animations', 'Inventory grid UI', 'Dialog system with choices'],
|
||||
},
|
||||
{
|
||||
id: 'multiplayer-feature',
|
||||
name: 'Multiplayer Feature',
|
||||
description: 'Generate multiplayer/networked features',
|
||||
category: 'multiplayer',
|
||||
template: `Generate a {{platform}} multiplayer feature:
|
||||
|
||||
**Feature:** {{feature}}
|
||||
**Players:** {{playerCount}}
|
||||
|
||||
Requirements:
|
||||
- Server-side validation
|
||||
- Client prediction where needed
|
||||
- Efficient networking
|
||||
- Cheat prevention
|
||||
|
||||
{{additionalRequirements}}`,
|
||||
examples: ['Team-based capture points', 'Trading system between players', 'Synchronized events'],
|
||||
},
|
||||
];
|
||||
|
||||
// Helper function to fill prompt template
|
||||
export function fillPromptTemplate(
|
||||
promptId: string,
|
||||
variables: Record<string, string>
|
||||
): string {
|
||||
const prompt = GENERATION_PROMPTS.find((p) => p.id === promptId);
|
||||
if (!prompt) return '';
|
||||
|
||||
let filled = prompt.template;
|
||||
for (const [key, value] of Object.entries(variables)) {
|
||||
filled = filled.split(`{{${key}}}`).join(value);
|
||||
}
|
||||
|
||||
return filled;
|
||||
}
|
||||
|
||||
// Code snippet library for common patterns
|
||||
export const CODE_SNIPPETS = {
|
||||
roblox: {
|
||||
'player-data-template': `-- Player Data Management
|
||||
local Players = game:GetService("Players")
|
||||
local DataStoreService = game:GetService("DataStoreService")
|
||||
|
||||
local DataStore = DataStoreService:GetDataStore("PlayerData")
|
||||
local PlayerData = {}
|
||||
|
||||
local DEFAULT_DATA = {
|
||||
coins = 0,
|
||||
level = 1,
|
||||
inventory = {}
|
||||
}
|
||||
|
||||
local function LoadData(player)
|
||||
local success, data = pcall(function()
|
||||
return DataStore:GetAsync("Player_" .. player.UserId)
|
||||
end)
|
||||
|
||||
if success and data then
|
||||
PlayerData[player] = data
|
||||
else
|
||||
PlayerData[player] = table.clone(DEFAULT_DATA)
|
||||
end
|
||||
end
|
||||
|
||||
local function SaveData(player)
|
||||
if not PlayerData[player] then return end
|
||||
|
||||
pcall(function()
|
||||
DataStore:SetAsync("Player_" .. player.UserId, PlayerData[player])
|
||||
end)
|
||||
end
|
||||
|
||||
Players.PlayerAdded:Connect(LoadData)
|
||||
Players.PlayerRemoving:Connect(function(player)
|
||||
SaveData(player)
|
||||
PlayerData[player] = nil
|
||||
end)
|
||||
|
||||
game:BindToClose(function()
|
||||
for _, player in ipairs(Players:GetPlayers()) do
|
||||
SaveData(player)
|
||||
end
|
||||
end)`,
|
||||
|
||||
'tween-utility': `-- Tween Utility Module
|
||||
local TweenService = game:GetService("TweenService")
|
||||
|
||||
local TweenUtility = {}
|
||||
|
||||
function TweenUtility.Tween(object, properties, duration, easingStyle, easingDirection)
|
||||
easingStyle = easingStyle or Enum.EasingStyle.Quad
|
||||
easingDirection = easingDirection or Enum.EasingDirection.Out
|
||||
|
||||
local tweenInfo = TweenInfo.new(duration, easingStyle, easingDirection)
|
||||
local tween = TweenService:Create(object, tweenInfo, properties)
|
||||
tween:Play()
|
||||
return tween
|
||||
end
|
||||
|
||||
function TweenUtility.FadeIn(guiObject, duration)
|
||||
guiObject.Visible = true
|
||||
return TweenUtility.Tween(guiObject, {BackgroundTransparency = 0}, duration or 0.3)
|
||||
end
|
||||
|
||||
function TweenUtility.FadeOut(guiObject, duration)
|
||||
local tween = TweenUtility.Tween(guiObject, {BackgroundTransparency = 1}, duration or 0.3)
|
||||
tween.Completed:Connect(function()
|
||||
guiObject.Visible = false
|
||||
end)
|
||||
return tween
|
||||
end
|
||||
|
||||
return TweenUtility`,
|
||||
|
||||
'signal-class': `-- Signal/Event Class
|
||||
local Signal = {}
|
||||
Signal.__index = Signal
|
||||
|
||||
function Signal.new()
|
||||
return setmetatable({
|
||||
_connections = {}
|
||||
}, Signal)
|
||||
end
|
||||
|
||||
function Signal:Connect(callback)
|
||||
local connection = {
|
||||
_callback = callback,
|
||||
_signal = self
|
||||
}
|
||||
|
||||
function connection:Disconnect()
|
||||
for i, conn in ipairs(self._signal._connections) do
|
||||
if conn == self then
|
||||
table.remove(self._signal._connections, i)
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
table.insert(self._connections, connection)
|
||||
return connection
|
||||
end
|
||||
|
||||
function Signal:Fire(...)
|
||||
for _, connection in ipairs(self._connections) do
|
||||
task.spawn(connection._callback, ...)
|
||||
end
|
||||
end
|
||||
|
||||
function Signal:Wait()
|
||||
local thread = coroutine.running()
|
||||
local connection
|
||||
connection = self:Connect(function(...)
|
||||
connection:Disconnect()
|
||||
task.spawn(thread, ...)
|
||||
end)
|
||||
return coroutine.yield()
|
||||
end
|
||||
|
||||
return Signal`,
|
||||
},
|
||||
|
||||
uefn: {
|
||||
'device-template': `# Basic Device Template
|
||||
using { /Fortnite.com/Devices }
|
||||
using { /Verse.org/Simulation }
|
||||
|
||||
device_name := class(creative_device):
|
||||
# Device references
|
||||
@editable TriggerDevice : trigger_device = trigger_device{}
|
||||
|
||||
# Variables
|
||||
var IsActive : logic = false
|
||||
|
||||
OnBegin<override>()<suspends> : void =
|
||||
TriggerDevice.TriggeredEvent.Subscribe(OnTriggered)
|
||||
Print("Device initialized")
|
||||
|
||||
OnTriggered(Agent : ?agent) : void =
|
||||
if (Player := player[Agent?]):
|
||||
set IsActive = not IsActive
|
||||
Print("Triggered by player")`,
|
||||
|
||||
'player-counter': `# Player Counter
|
||||
using { /Fortnite.com/Devices }
|
||||
using { /Fortnite.com/Game }
|
||||
using { /Verse.org/Simulation }
|
||||
|
||||
player_counter := class(creative_device):
|
||||
var PlayerCount : int = 0
|
||||
|
||||
OnBegin<override>()<suspends> : void =
|
||||
GetPlayspace().PlayerAddedEvent().Subscribe(OnPlayerAdded)
|
||||
GetPlayspace().PlayerRemovedEvent().Subscribe(OnPlayerRemoved)
|
||||
|
||||
OnPlayerAdded(Player : player) : void =
|
||||
set PlayerCount += 1
|
||||
Print("Players: {PlayerCount}")
|
||||
|
||||
OnPlayerRemoved(Player : player) : void =
|
||||
set PlayerCount -= 1
|
||||
Print("Players: {PlayerCount}")`,
|
||||
},
|
||||
|
||||
spatial: {
|
||||
'component-template': `// Basic Component Template
|
||||
import { Component, NetworkedComponent } from 'spatial';
|
||||
|
||||
export class MyComponent extends Component {
|
||||
// Properties
|
||||
private health: number = 100;
|
||||
private isActive: boolean = true;
|
||||
|
||||
onStart(): void {
|
||||
console.log('Component started');
|
||||
this.setupListeners();
|
||||
}
|
||||
|
||||
onUpdate(deltaTime: number): void {
|
||||
if (!this.isActive) return;
|
||||
// Update logic here
|
||||
}
|
||||
|
||||
onDestroy(): void {
|
||||
console.log('Component destroyed');
|
||||
}
|
||||
|
||||
private setupListeners(): void {
|
||||
// Setup event listeners
|
||||
}
|
||||
}`,
|
||||
|
||||
'networked-state': `// Networked State Management
|
||||
import { NetworkedComponent, networked, rpc, RpcMode } from 'spatial';
|
||||
|
||||
export class GameState extends NetworkedComponent {
|
||||
@networked
|
||||
private score: number = 0;
|
||||
|
||||
@networked
|
||||
private gamePhase: string = 'waiting';
|
||||
|
||||
@rpc(RpcMode.Server)
|
||||
addScore(amount: number): void {
|
||||
this.score += amount;
|
||||
this.onScoreChanged();
|
||||
}
|
||||
|
||||
@rpc(RpcMode.AllClients)
|
||||
onScoreChanged(): void {
|
||||
console.log(\`Score updated: \${this.score}\`);
|
||||
}
|
||||
|
||||
@rpc(RpcMode.Server)
|
||||
setGamePhase(phase: string): void {
|
||||
this.gamePhase = phase;
|
||||
}
|
||||
}`,
|
||||
},
|
||||
};
|
||||
|
||||
// Get snippets for a platform
|
||||
export function getSnippetsForPlatform(platform: string): Record<string, string> {
|
||||
return CODE_SNIPPETS[platform as keyof typeof CODE_SNIPPETS] || {};
|
||||
}
|
||||
|
||||
// Get common patterns for a platform
|
||||
export function getPatternsForPlatform(
|
||||
platform: string
|
||||
): Record<string, string> {
|
||||
const patterns = PLATFORM_PATTERNS[platform as keyof typeof PLATFORM_PATTERNS];
|
||||
return patterns?.commonPatterns || {};
|
||||
}
|
||||
1649
src/lib/ai-generation/system-templates.ts
Normal file
1649
src/lib/ai-generation/system-templates.ts
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue