From fb17d0e0756774067b49fff0deb9c8c256401b03 Mon Sep 17 00:00:00 2001 From: MrPiglr Date: Sat, 17 Jan 2026 02:52:12 +0000 Subject: [PATCH] Generated by Spark: ? --- PRD.md | 125 ++++++++++++++++++ index.html | 3 +- package-lock.json | 72 +++++++++++ package.json | 1 + spark.meta.json | 5 +- src/App.tsx | 69 +++++++++- src/components/AIChat.tsx | 127 ++++++++++++++++++ src/components/CodeEditor.tsx | 63 +++++++++ src/components/TemplatesDrawer.tsx | 82 ++++++++++++ src/components/Toolbar.tsx | 130 +++++++++++++++++++ src/components/WelcomeDialog.tsx | 86 +++++++++++++ src/index.css | 102 ++++++++++++++- src/lib/templates.ts | 200 +++++++++++++++++++++++++++++ 13 files changed, 1059 insertions(+), 6 deletions(-) create mode 100644 PRD.md create mode 100644 src/components/AIChat.tsx create mode 100644 src/components/CodeEditor.tsx create mode 100644 src/components/TemplatesDrawer.tsx create mode 100644 src/components/Toolbar.tsx create mode 100644 src/components/WelcomeDialog.tsx create mode 100644 src/lib/templates.ts diff --git a/PRD.md b/PRD.md new file mode 100644 index 0000000..3d4acbb --- /dev/null +++ b/PRD.md @@ -0,0 +1,125 @@ +# Planning Guide + +AeThex Studio is a browser-based Roblox Lua script editor with integrated AI assistance for rapid prototyping and learning. + +**Experience Qualities**: +1. **Professional** - Should feel like a legitimate development tool with clean typography and purposeful spacing +2. **Intelligent** - AI assistance should feel seamlessly integrated, not bolted on +3. **Empowering** - Users should feel confident writing and understanding Lua code + +**Complexity Level**: Light Application (multiple features with basic state) +This is a focused code editor with AI chat, script templates, and syntax validation - not a full-featured IDE with file systems, debugging, or deployment pipelines. + +## Essential Features + +### Monaco Code Editor +- **Functionality**: Full-featured Lua code editor with syntax highlighting, autocomplete, and line numbers +- **Purpose**: Provide a professional coding environment for Roblox scripting +- **Trigger**: Loads on app start with default template +- **Progression**: User opens app → sees editor with starter code → begins typing → receives syntax highlighting and autocomplete +- **Success criteria**: Code is editable, syntax-highlighted, and changes persist between sessions + +### AI Chat Assistant +- **Functionality**: Conversational AI panel that helps explain code, debug issues, and suggest improvements +- **Purpose**: Lower the learning curve for new Roblox developers +- **Trigger**: User clicks chat button or types a question in the AI panel +- **Progression**: User asks question → AI analyzes current code → provides contextual answer with code examples +- **Success criteria**: AI responses are relevant, helpful, and include code snippets when appropriate + +### Script Templates +- **Functionality**: Pre-built Roblox script templates (basic part manipulation, player join events, GUI interactions, etc.) +- **Purpose**: Jump-start development and teach common patterns +- **Trigger**: User clicks "Templates" button +- **Progression**: User browses templates → clicks template → code loads into editor → user modifies for their needs +- **Success criteria**: Templates load instantly and represent common Roblox use cases + +### Code Validation +- **Functionality**: Real-time syntax checking and error highlighting +- **Purpose**: Catch errors before testing in Roblox Studio +- **Trigger**: Automatic as user types +- **Progression**: User types invalid Lua → error highlights appear → user hovers for details → fixes error +- **Success criteria**: Common Lua syntax errors are caught and clearly displayed + +### Export/Copy Code +- **Functionality**: One-click copy of current code to clipboard +- **Purpose**: Easy transfer to Roblox Studio +- **Trigger**: User clicks copy button +- **Progression**: User finishes editing → clicks copy → receives confirmation → pastes into Roblox Studio +- **Success criteria**: Code copies to clipboard with proper formatting + +## Edge Case Handling +- **Empty Editor**: Show helpful placeholder text with getting started tips +- **AI Errors**: Display user-friendly error messages if AI service is unavailable +- **Large Scripts**: Monaco handles large files, but warn if script exceeds 5000 lines (unusual for Roblox scripts) +- **Invalid Lua**: Show errors inline without blocking the user from continuing to type +- **Lost Work**: Auto-save code every 30 seconds to prevent data loss + +## Design Direction +The design should evoke a sense of technical sophistication meets creative playfulness - this is a tool for building games, not enterprise software. Think dark themes with neon accents, geometric patterns, and smooth transitions that feel modern and game-inspired. + +## Color Selection +A dark, code-focused theme with vibrant accent colors inspired by Roblox's playful brand and gaming aesthetics. + +- **Primary Color**: oklch(0.45 0.20 265) - Deep electric blue that represents technical precision and digital creativity +- **Secondary Colors**: + - Background: oklch(0.15 0.02 265) - Near-black with subtle blue tint + - Surface: oklch(0.20 0.03 265) - Slightly elevated dark panels +- **Accent Color**: oklch(0.75 0.20 150) - Vibrant cyan-green for CTAs, highlights, and active states +- **Foreground/Background Pairings**: + - Primary (Deep Blue): White text (oklch(0.98 0 0)) - Ratio 7.2:1 ✓ + - Accent (Cyan-Green): Dark text (oklch(0.15 0.02 265)) - Ratio 8.1:1 ✓ + - Background (Near-Black): Light text (oklch(0.85 0.03 265)) - Ratio 12.3:1 ✓ + - Surface (Dark Panel): Light text (oklch(0.85 0.03 265)) - Ratio 10.5:1 ✓ + +## Font Selection +Typography should feel technical yet approachable - monospace for code, clean sans-serif for UI. + +- **Typographic Hierarchy**: + - H1 (App Title): Space Grotesk Bold/32px/tight tracking (-0.02em) + - H2 (Section Headers): Space Grotesk Semibold/20px/normal tracking + - Body (UI Text): Inter Medium/14px/relaxed line-height (1.6) + - Code (Editor): JetBrains Mono Regular/14px/monospace + - Small (Hints): Inter Regular/12px/muted color + +## Animations +Animations should feel snappy and technical - quick state transitions with subtle easing that reinforces the digital tool aesthetic. Use framer-motion for panel slides and modal appearances, but keep transitions under 200ms to avoid feeling sluggish. Add micro-interactions on buttons (scale on press) and smooth scrolling in the chat panel. + +## Component Selection +- **Components**: + - `Button` (primary actions like "Run", "Copy", "Ask AI") with custom variants for destructive and accent states + - `Card` for template previews and code snippets + - `Separator` for dividing editor from chat panel + - `ScrollArea` for chat messages and template list + - `Tabs` for switching between templates and settings + - `Dialog` for first-time user onboarding + - `Badge` for Lua syntax error indicators + - `Tooltip` for icon button explanations + - Custom Monaco Editor wrapper component + - Custom AI chat component with message bubbles +- **Customizations**: + - Monaco editor needs dark theme configuration + - Chat bubbles with distinct styling for user vs AI messages + - Template cards with code preview using syntax-highlighted pre blocks + - Resizable panels using react-resizable-panels +- **States**: + - Buttons: Default (solid accent), Hover (brightened +10% lightness), Active (scale 0.97), Disabled (50% opacity) + - Editor: Default (dark background), Focused (subtle glow border) + - Chat input: Default (muted border), Focused (accent border with glow) +- **Icon Selection**: + - Code (editor): `` from Phosphor + - Chat: `` from Phosphor + - Copy: `` from Phosphor + - Templates: `` from Phosphor + - AI sparkle: `` from Phosphor + - Play/Run: `` from Phosphor +- **Spacing**: + - Container padding: p-4 (16px) + - Component gaps: gap-4 (16px) for major sections, gap-2 (8px) for grouped items + - Button padding: px-6 py-2.5 + - Card padding: p-6 +- **Mobile**: + - Stack editor and chat vertically on mobile + - Make chat panel collapsible on small screens + - Reduce font sizes: H1 to 24px, body to 13px + - Full-width buttons with bottom sheet for templates + - Hide Monaco minimap on screens < 768px diff --git a/index.html b/index.html index f62002d..a829f30 100644 --- a/index.html +++ b/index.html @@ -4,9 +4,10 @@ - + AeThex Studio - Roblox Lua Editor + diff --git a/package-lock.json b/package-lock.json index c92f18e..e5a1a68 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,7 @@ "@github/spark": ">=0.43.1 <1", "@heroicons/react": "^2.2.0", "@hookform/resolvers": "^4.1.3", + "@monaco-editor/react": "^4.7.0", "@octokit/core": "^6.1.4", "@phosphor-icons/react": "^2.1.7", "@radix-ui/colors": "^3.0.0", @@ -1076,6 +1077,29 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@monaco-editor/loader": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@monaco-editor/loader/-/loader-1.7.0.tgz", + "integrity": "sha512-gIwR1HrJrrx+vfyOhYmCZ0/JcWqG5kbfG7+d3f/C1LXk2EvzAbHSg3MQ5lO2sMlo9izoAZ04shohfKLVT6crVA==", + "license": "MIT", + "dependencies": { + "state-local": "^1.0.6" + } + }, + "node_modules/@monaco-editor/react": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@monaco-editor/react/-/react-4.7.0.tgz", + "integrity": "sha512-cyzXQCtO47ydzxpQtCGSQGOC8Gk3ZUeBXFAxD+CWXYFo5OqZyZUonFl0DwUlTyAfRHntBfw2p3w4s9R6oe1eCA==", + "license": "MIT", + "dependencies": { + "@monaco-editor/loader": "^1.5.0" + }, + "peerDependencies": { + "monaco-editor": ">= 0.25.0 < 1", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -4404,6 +4428,14 @@ "@types/node": "*" } }, + "node_modules/@types/trusted-types": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", + "license": "MIT", + "optional": true, + "peer": true + }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "8.48.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.48.0.tgz", @@ -5992,6 +6024,16 @@ "csstype": "^3.0.2" } }, + "node_modules/dompurify": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.7.tgz", + "integrity": "sha512-WhL/YuveyGXJaerVlMYGWhvQswa7myDG17P7Vu65EWC05o8vfeNbvNf4d/BOvH99+ZW+LlQsc1GDKMa1vNK6dw==", + "license": "(MPL-2.0 OR Apache-2.0)", + "peer": true, + "optionalDependencies": { + "@types/trusted-types": "^2.0.7" + } + }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -7953,6 +7995,30 @@ "node": "*" } }, + "node_modules/monaco-editor": { + "version": "0.55.1", + "resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.55.1.tgz", + "integrity": "sha512-jz4x+TJNFHwHtwuV9vA9rMujcZRb0CEilTEwG2rRSpe/A7Jdkuj8xPKttCgOh+v/lkHy7HsZ64oj+q3xoAFl9A==", + "license": "MIT", + "peer": true, + "dependencies": { + "dompurify": "3.2.7", + "marked": "14.0.0" + } + }, + "node_modules/monaco-editor/node_modules/marked": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/marked/-/marked-14.0.0.tgz", + "integrity": "sha512-uIj4+faQ+MgHgwUW1l2PsPglZLOLOT1uErt06dAPtx2kjteLAkbsd/0FiYg/MGS+i7ZKLb7w2WClxHkzOOuryQ==", + "license": "MIT", + "peer": true, + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 18" + } + }, "node_modules/motion-dom": { "version": "12.23.23", "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.23.23.tgz", @@ -9198,6 +9264,12 @@ "dev": true, "license": "MIT" }, + "node_modules/state-local": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/state-local/-/state-local-1.0.7.tgz", + "integrity": "sha512-HTEHMNieakEnoe33shBYcZ7NX83ACUjCu8c40iOGEZsngj9zRnkqS9j1pqQPXwobB0ZcVTk27REb7COQ0UR59w==", + "license": "MIT" + }, "node_modules/statuses": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", diff --git a/package.json b/package.json index 165aec8..6fac6a5 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "@github/spark": ">=0.43.1 <1", "@heroicons/react": "^2.2.0", "@hookform/resolvers": "^4.1.3", + "@monaco-editor/react": "^4.7.0", "@octokit/core": "^6.1.4", "@phosphor-icons/react": "^2.1.7", "@radix-ui/colors": "^3.0.0", diff --git a/spark.meta.json b/spark.meta.json index 706a311..a277bc5 100644 --- a/spark.meta.json +++ b/spark.meta.json @@ -1,3 +1,4 @@ { - "templateVersion": 1 -} + "templateVersion": 1, + "dbType": "kv" +} \ No newline at end of file diff --git a/src/App.tsx b/src/App.tsx index 98ef973..0831574 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,5 +1,70 @@ +import { useState } from 'react'; +import { Toaster } from '@/components/ui/sonner'; +import { CodeEditor } from '@/components/CodeEditor'; +import { AIChat } from '@/components/AIChat'; +import { Toolbar } from '@/components/Toolbar'; +import { TemplatesDrawer } from '@/components/TemplatesDrawer'; +import { WelcomeDialog } from '@/components/WelcomeDialog'; +import { ResizablePanelGroup, ResizablePanel, ResizableHandle } from '@/components/ui/resizable'; +import { useKV } from '@github/spark/hooks'; +import { useIsMobile } from '@/hooks/use-mobile'; +import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; + function App() { - return
+ const [currentCode, setCurrentCode] = useState(''); + const [showTemplates, setShowTemplates] = useState(false); + const [code, setCode] = useKV('aethex-current-code', ''); + const isMobile = useIsMobile(); + + const handleTemplateSelect = (templateCode: string) => { + setCode(templateCode); + setCurrentCode(templateCode); + }; + + return ( +
+ setShowTemplates(true)} /> + +
+ {isMobile ? ( + + + Editor + AI Assistant + + + + + + + + + ) : ( + + + + + + + + + + + + )} +
+ + {showTemplates && ( + setShowTemplates(false)} + /> + )} + + + +
+ ); } -export default App \ No newline at end of file +export default App; \ No newline at end of file diff --git a/src/components/AIChat.tsx b/src/components/AIChat.tsx new file mode 100644 index 0000000..49ed590 --- /dev/null +++ b/src/components/AIChat.tsx @@ -0,0 +1,127 @@ +import { useState } from 'react'; +import { Button } from '@/components/ui/button'; +import { ScrollArea } from '@/components/ui/scroll-area'; +import { Textarea } from '@/components/ui/textarea'; +import { Sparkle, PaperPlaneRight } from '@phosphor-icons/react'; +import { toast } from 'sonner'; + +interface Message { + role: 'user' | 'assistant'; + content: string; +} + +interface AIChatProps { + currentCode: string; +} + +export function AIChat({ currentCode }: AIChatProps) { + const [messages, setMessages] = useState([ + { + role: 'assistant', + content: 'Hi! I\'m your AI assistant for Roblox Lua development. Ask me anything about your code, Roblox scripting, or game development!', + }, + ]); + const [input, setInput] = useState(''); + const [isLoading, setIsLoading] = useState(false); + + const handleSend = async () => { + if (!input.trim() || isLoading) return; + + const userMessage = input.trim(); + setInput(''); + setMessages((prev) => [...prev, { role: 'user', content: userMessage }]); + setIsLoading(true); + + try { + const promptText = `You are an expert Roblox Lua developer helping a user with their code. The user is working on this code: + +\`\`\`lua +${currentCode} +\`\`\` + +User question: ${userMessage} + +Provide helpful, concise answers. Include code examples when relevant. Keep responses friendly and encouraging.`; + + const response = await window.spark.llm(promptText, 'gpt-4o-mini'); + setMessages((prev) => [...prev, { role: 'assistant', content: response }]); + } catch (error) { + console.error('AI Error:', error); + toast.error('Failed to get AI response. Please try again.'); + setMessages((prev) => [...prev, { role: 'assistant', content: 'Sorry, I encountered an error. Please try asking again.' }]); + } finally { + setIsLoading(false); + } + }; + + const handleKeyDown = (e: React.KeyboardEvent) => { + if (e.key === 'Enter' && !e.shiftKey) { + e.preventDefault(); + handleSend(); + } + }; + + return ( +
+
+ +

AI Assistant

+
+ + +
+ {messages.map((message, index) => ( +
+
+

{message.content}

+
+
+ ))} + {isLoading && ( +
+
+
+
+
+
+
+
+
+ )} +
+ + +
+
+