new file: app/ide/page.tsx

This commit is contained in:
Anderson 2026-01-28 05:02:13 +00:00 committed by GitHub
parent 4bc31a32e2
commit ea5ba62c54
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
48 changed files with 567 additions and 417 deletions

View file

@ -2,8 +2,9 @@
This is a NextJS starter in Firebase Studio.
To get started, take a look at src/app/page.tsx.
**Platform Switching** - Work with Roblox, UEFN, Spatial, or Core
- **Platform Switching** - Work with Roblox, UEFN, Spatial, or Core
- **Platform-Specific Templates**:
- 🎮 **Roblox**: 25 Lua templates
@ -14,26 +15,34 @@ To get started, take a look at src/app/page.tsx.
- **Side-by-Side Comparison** - Compare original and translated code
- **Smart Editor** - Language highlighting adapts to selected platform
### 🎨 **Modern Code Editor**
## 🎨 **Modern Code Editor**
- **Monaco Editor** - The same editor that powers VS Code
- **Multi-language Support** - Lua, Verse, TypeScript
- **Real-time code validation** and linting
- **Multi-file editing** with tab management
- **File tree navigation** with drag-and-drop organization
### 🤖 **AI-Powered Assistant**
## 🤖 **AI-Powered Assistant**
- Built-in AI chat for code help and debugging
- Context-aware suggestions
- Code explanation and documentation
- Roblox API knowledge
### 📁 **Project Management**
## 📁 **Project Management**
- **File Tree** - Organize your scripts into folders
- **Drag-and-drop** - Rearrange files easily
- **Quick file search** (Cmd/Ctrl+P) - Find files instantly
- **Search in files** (Cmd/Ctrl+Shift+F) - Global text search
### 🎯 **Productivity Features**
## 🎯 **Productivity Features**
- **33+ Code Templates** - Ready-made scripts for multiple platforms
- **Roblox** (25 templates):
- Beginner templates (Hello World, Touch Detectors, etc.)
@ -49,7 +58,9 @@ To get started, take a look at src/app/page.tsx.
- **Keyboard Shortcuts** - Professional IDE shortcuts
- **Code Preview** - Test your scripts instantly
### 💻 **Interactive Terminal & CLI**
## 💻 **Interactive Terminal & CLI**
- **Built-in Terminal** - Full-featured command line interface
- **10+ CLI Commands** for Roblox development:
- `help` - Display available commands
@ -67,7 +78,9 @@ To get started, take a look at src/app/page.tsx.
- **Smart Suggestions** - Context-aware command hints
- **Toggle with Cmd/Ctrl + `** - Quick terminal access
### 🎨 **Customization**
## 🎨 **Customization**
- **5 Beautiful Themes**:
- **Dark** - Classic dark theme for comfortable coding
- **Light** - Clean light theme for bright environments
@ -76,21 +89,27 @@ To get started, take a look at src/app/page.tsx.
- **Ocean** - Deep blue theme
- **Persistent preferences** - Your settings are saved
### 📱 **Mobile Responsive**
## 📱 **Mobile Responsive**
- Optimized layouts for phones and tablets
- Touch-friendly controls
- Hamburger menu for mobile
- Collapsible panels
### 🚀 **Developer Experience**
## 🚀 **Developer Experience**
- **Code splitting** for fast loading
- **Error boundaries** with graceful error handling
- **Loading states** with spinners
- **Toast notifications** for user feedback
- **Testing infrastructure** with Vitest
## 🎮 Perfect For
- **Multi-Platform Developers** - Build for Roblox, UEFN, Spatial, and Core from one IDE
- **Game Studios** - Translate games between platforms with AI assistance
- **Roblox → UEFN Migration** - Converting existing Roblox games to Fortnite
@ -98,26 +117,32 @@ To get started, take a look at src/app/page.tsx.
- **Rapid Prototyping** - Build once, deploy to multiple platforms
- **Web-Based Development** - Code anywhere, no installation needed
## ⌨️ Keyboard Shortcuts
| Shortcut | Action |
|----------|--------|
| `Cmd/Ctrl + S` | Save file (auto-save enabled) |
| `Cmd/Ctrl + P` | Quick file search |
| `Cmd/Ctrl + K` | Command palette |
| `Cmd/Ctrl + N` | New project |
| `Cmd/Ctrl + F` | Find in editor |
| `Cmd/Ctrl + Shift + F` | Search in all files |
| ``Cmd/Ctrl + ` `` | Toggle terminal |
| Shortcut | Action |
| :----------------- | :---------------------------- |
| `Cmd/Ctrl + S` | Save file (auto-save enabled) |
| `Cmd/Ctrl + P` | Quick file search |
| `Cmd/Ctrl + K` | Command palette |
| `Cmd/Ctrl + N` | New project |
| `Cmd/Ctrl + F` | Find in editor |
| `Cmd/Ctrl + Shift + F` | Search in all files |
| ``Cmd/Ctrl + ` `` | Toggle terminal |
## 🚀 Getting Started
### Prerequisites
- Node.js 18+
- npm or yarn
### Installation
#
```bash
# Clone the repository
git clone https://github.com/AeThex-LABS/aethex-studio.git
@ -134,6 +159,7 @@ npm run dev
Visit `http://localhost:3000` to see the application.
### 🔑 Enabling Cross-Platform Translation
To unlock the **AI-powered code translation** feature, you need a Claude API key:
@ -141,7 +167,7 @@ To unlock the **AI-powered code translation** feature, you need a Claude API key
1. **Get API Key**: Visit [Anthropic Console](https://console.anthropic.com/settings/keys) and create a new API key
2. **Configure Environment**:
```bash
```bash
# Copy example environment file
cp .env.example .env.local
@ -150,7 +176,7 @@ To unlock the **AI-powered code translation** feature, you need a Claude API key
```
3. **Restart Dev Server**:
```bash
```bash
npm run dev
```
@ -163,8 +189,10 @@ To unlock the **AI-powered code translation** feature, you need a Claude API key
💡 **Note**: Without an API key, the app works perfectly but shows mock translations instead of real AI conversions.
### Building for Production
#
```bash
# Build the application
npm run build
@ -173,8 +201,10 @@ npm run build
npm start
```
## 📖 Usage Guide
### Creating Your First Script
1. Click **"New File"** in the file tree
@ -183,6 +213,7 @@ npm start
4. Click **"Preview"** to test
5. **Copy** or **Export** your script
### Using Templates
1. Click the **Templates** button in the toolbar
@ -190,6 +221,7 @@ npm start
3. Click a template to load it into your editor
4. Customize the code for your needs
### AI Assistant
1. Open the **AI Chat** panel (right side on desktop)
@ -200,6 +232,7 @@ npm start
- Best practices
3. Get instant, context-aware answers
### Organizing Files
- **Create folders** - Right-click in file tree
@ -207,12 +240,14 @@ npm start
- **Rename** - Click the menu (⋯) next to a file
- **Delete** - Use the menu to remove files
### Searching
- **Quick search** - `Cmd/Ctrl+P` to find files by name
- **Global search** - `Cmd/Ctrl+Shift+F` to search text across all files
- **In-editor search** - `Cmd/Ctrl+F` to find text in current file
## 🛠️ Tech Stack
- **Next.js 14** - React framework
@ -226,8 +261,10 @@ npm start
- **PostHog** - Analytics (optional)
- **Sentry** - Error tracking (optional)
## 🧪 Running Tests
#
```bash
# Run all tests
npm test
@ -242,8 +279,10 @@ npm run test:ui
npm run test:coverage
```
## 📂 Project Structure
#
```
aethex-studio/
├── src/
@ -264,6 +303,7 @@ aethex-studio/
└── tests/ # Test files
```
## 🤝 Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
@ -274,37 +314,52 @@ Contributions are welcome! Please feel free to submit a Pull Request.
4. Push to the branch (`git push origin feature/AmazingFeature`)
5. Open a Pull Request
## 📝 Code Templates
AeThex Studio includes 25+ production-ready templates:
**Beginner:**
- Hello World, Player Join Handler, Part Touch Detector, etc.
**Gameplay:**
- DataStore System, Teleport Part, Team System, Combat System, etc.
**UI:**
- GUI Buttons, Proximity Prompts, Countdown Timers, etc.
**Tools:**
- Give Tool, Sound Manager, Admin Commands, Chat Commands, etc.
**Advanced:**
- Round System, Inventory System, Pathfinding NPC, Shop System, etc.
## 🐛 Bug Reports
Found a bug? Please open an issue on GitHub with:
- Description of the bug
- Steps to reproduce
- Expected vs actual behavior
- Screenshots (if applicable)
## 📜 License
This project is licensed under the MIT License - see the LICENSE file for details.
## 🙏 Acknowledgments
- **Monaco Editor** - For the powerful code editor
@ -312,12 +367,16 @@ This project is licensed under the MIT License - see the LICENSE file for detail
- **Radix UI** - For accessible component primitives
- **Vercel** - For Next.js framework
## 📧 Contact
- **Website**: [aethex.com](https://aethex.com)
- **GitHub**: [@AeThex-LABS](https://github.com/AeThex-LABS)
- **Issues**: [GitHub Issues](https://github.com/AeThex-LABS/aethex-studio/issues)
---
---
**Built with ❤️ by the AeThex team**

4
app/dashboard/page.tsx Normal file
View file

@ -0,0 +1,4 @@
import { DashboardPage } from "../../src/components/aethex/dashboard-page";
export default function Page() {
return <DashboardPage />;
}

4
app/ide/page.tsx Normal file
View file

@ -0,0 +1,4 @@
import App from "../../src/App";
export default function Page() {
return <App />;
}

View file

@ -1,22 +1,10 @@
import type { Metadata } from "next";
import { Inter, JetBrains_Mono } from "next/font/google";
import Toaster from "../src/components/ui/toaster";
import "./globals.css";
import "./studio-theme.css";
import StudioLayout from "../components/StudioLayout";
const inter = Inter({
subsets: ["latin"],
variable: "--font-inter",
});
const jetbrainsMono = JetBrains_Mono({
subsets: ["latin"],
variable: "--font-jetbrains-mono",
});
export const metadata: Metadata = {
title: "AeThex Studio - Cross-Platform Game Development IDE",
description: "Professional game development IDE for Roblox, Web, Mobile, and Desktop",
title: "AeThex Studio",
description: "The Next-Generation Cross-Platform IDE",
};
export default function RootLayout({
@ -26,8 +14,21 @@ export default function RootLayout({
}>) {
return (
<html lang="en" className="dark">
<body className={`${inter.variable} ${jetbrainsMono.variable} font-sans antialiased bg-background text-white`}>
<head>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link
rel="preconnect"
href="https://fonts.gstatic.com"
crossOrigin="anonymous"
/>
<link
href="https://fonts.googleapis.com/css2?family=Inter:wght@400;700&family=Source+Code+Pro:wght@400&family=Space+Grotesk:wght@400;700&display=swap"
rel="stylesheet"
/>
</head>
<body className="font-body antialiased">
{children}
<Toaster />
</body>
</html>
);

View file

@ -1,5 +1,5 @@
import { LoginPage } from "@/components/aethex/login-page";
import App from "../src/App";
export default function Page() {
return <LoginPage />;
return <App />;
}

View file

@ -6,7 +6,7 @@ import { useEditorStore, FileNode } from '@/store/editor-store';
import { cn, getFileIcon, getPlatformIcon } from '@/lib/utils';
export function FileTree() {
const { files, openFile, moveFile } = useEditorStore();
const { files, openFile } = useEditorStore();
const [expandedFolders, setExpandedFolders] = React.useState<Set<string>>(
new Set(['roblox', 'web', 'mobile', 'desktop', 'shared'])
);
@ -29,10 +29,7 @@ export function FileTree() {
return (
<div key={node.id} draggable onDragStart={e => e.dataTransfer.setData('fileId', node.id)} onDrop={e => {
e.preventDefault();
const fileId = e.dataTransfer.getData('fileId');
if (fileId && fileId !== node.id && isFolder) {
moveFile(fileId, node.id);
}
// moveFile functionality is not implemented
}} onDragOver={e => isFolder && e.preventDefault()}>
<div
className={cn(

View file

@ -2,19 +2,19 @@
import { useEditorStore } from "../store/editor-zustand";
function StudioEditor() {
const openTabs = useEditorStore((s) => s.openTabs);
const activeTabId = useEditorStore((s) => s.activeTabId);
const setActiveTab = useEditorStore((s) => s.setActiveTab);
const closeTab = useEditorStore((s) => s.closeTab);
const openTabs = useEditorStore((s: any) => s.openTabs);
const activeTabId = useEditorStore((s: any) => s.activeTabId);
const setActiveTab = useEditorStore((s: any) => s.setActiveTab);
const closeTab = useEditorStore((s: any) => s.closeTab);
// Find active file
const activeFile = openTabs.find(f => f.id === activeTabId);
const activeFile = openTabs.find((f: any) => f.id === activeTabId);
return (
<div className="editor-area">
<div className="editor-tabs">
{openTabs.length === 0 && (
<div className="editor-tab empty">No files open</div>
)}
{openTabs.map((file) => (
{openTabs.map((file: any) => (
<div
key={file.id}
className={"editor-tab" + (activeTabId === file.id ? " active" : "")}
@ -33,7 +33,7 @@ function StudioEditor() {
</div>
<div className="editor-content">
{activeFile ? (
activeFile.content.split("\n").map((line, i) => (
activeFile.content.split("\n").map((line: string, i: number) => (
<div className="code-line" key={i}>
<div className="line-number">{i + 1}</div>
<div className="line-content">{line}</div>

125
package-lock.json generated
View file

@ -11,6 +11,8 @@
"@genkit-ai/google-genai": "^1.20.0",
"@genkit-ai/next": "^1.20.0",
"@hookform/resolvers": "^4.1.3",
"@monaco-editor/react": "^4.7.0",
"@phosphor-icons/react": "^2.1.10",
"@radix-ui/react-accordion": "^1.2.3",
"@radix-ui/react-alert-dialog": "^1.1.6",
"@radix-ui/react-avatar": "^1.1.3",
@ -51,9 +53,11 @@
"react-hook-form": "^7.54.2",
"react-syntax-highlighter": "^15.5.0",
"recharts": "^2.15.1",
"sonner": "^2.0.7",
"tailwind-merge": "^3.0.1",
"tailwindcss-animate": "^1.0.7",
"zod": "^3.24.2"
"zod": "^3.24.2",
"zustand": "^5.0.10"
},
"devDependencies": {
"@types/node": "^20",
@ -3408,6 +3412,29 @@
"node": ">= 0.6"
}
},
"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/@next/env": {
"version": "15.5.9",
"resolved": "https://registry.npmjs.org/@next/env/-/env-15.5.9.tgz",
@ -5912,6 +5939,19 @@
"node": ">=14"
}
},
"node_modules/@phosphor-icons/react": {
"version": "2.1.10",
"resolved": "https://registry.npmjs.org/@phosphor-icons/react/-/react-2.1.10.tgz",
"integrity": "sha512-vt8Tvq8GLjheAZZYa+YG/pW7HDbov8El/MANW8pOAz4eGxrwhnbfrQZq0Cp4q8zBEu8NIhHdnr+r8thnfRSNYA==",
"license": "MIT",
"engines": {
"node": ">=10"
},
"peerDependencies": {
"react": ">= 16.8",
"react-dom": ">= 16.8"
}
},
"node_modules/@pkgjs/parseargs": {
"version": "0.11.0",
"license": "MIT",
@ -7688,6 +7728,13 @@
"devOptional": true,
"license": "MIT"
},
"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
},
"node_modules/@types/unist": {
"version": "2.0.11",
"resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz",
@ -8980,6 +9027,15 @@
"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)",
"optionalDependencies": {
"@types/trusted-types": "^2.0.7"
}
},
"node_modules/dot-prop": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-6.0.1.tgz",
@ -11511,6 +11567,28 @@
"integrity": "sha512-EGWKgxALGMgzvxYF1UyGTy0HXX/2vHLkw6+NvDKW2jypWbHpjQuj4UMcqQWXHERJhVGKikolT06G3bcKe4fi7w==",
"license": "MIT"
},
"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",
"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",
"bin": {
"marked": "bin/marked.js"
},
"engines": {
"node": ">= 18"
}
},
"node_modules/ms": {
"version": "2.1.3",
"license": "MIT"
@ -13213,6 +13291,16 @@
"node": ">=6"
}
},
"node_modules/sonner": {
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/sonner/-/sonner-2.0.7.tgz",
"integrity": "sha512-W6ZN4p58k8aDKA4XPcx2hpIQXBRAgyiWVkYhT7CvK6D3iAu7xjvVyhQHg2/iaKJZ1XVJ4r7XuwGL+WGEK37i9w==",
"license": "MIT",
"peerDependencies": {
"react": "^18.0.0 || ^19.0.0 || ^19.0.0-rc",
"react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-rc"
}
},
"node_modules/source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
@ -13262,6 +13350,12 @@
"node": "*"
}
},
"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",
@ -14429,6 +14523,35 @@
"peerDependencies": {
"zod": "^3.25 || ^4"
}
},
"node_modules/zustand": {
"version": "5.0.10",
"resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.10.tgz",
"integrity": "sha512-U1AiltS1O9hSy3rul+Ub82ut2fqIAefiSuwECWt6jlMVUGejvf+5omLcRBSzqbRagSM3hQZbtzdeRc6QVScXTg==",
"license": "MIT",
"engines": {
"node": ">=12.20.0"
},
"peerDependencies": {
"@types/react": ">=18.0.0",
"immer": ">=9.0.6",
"react": ">=18.0.0",
"use-sync-external-store": ">=1.2.0"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"immer": {
"optional": true
},
"react": {
"optional": true
},
"use-sync-external-store": {
"optional": true
}
}
}
}
}

View file

@ -15,6 +15,8 @@
"@genkit-ai/google-genai": "^1.20.0",
"@genkit-ai/next": "^1.20.0",
"@hookform/resolvers": "^4.1.3",
"@monaco-editor/react": "^4.7.0",
"@phosphor-icons/react": "^2.1.10",
"@radix-ui/react-accordion": "^1.2.3",
"@radix-ui/react-alert-dialog": "^1.1.6",
"@radix-ui/react-avatar": "^1.1.3",
@ -55,9 +57,11 @@
"react-hook-form": "^7.54.2",
"react-syntax-highlighter": "^15.5.0",
"recharts": "^2.15.1",
"sonner": "^2.0.7",
"tailwind-merge": "^3.0.1",
"tailwindcss-animate": "^1.0.7",
"zod": "^3.24.2"
"zod": "^3.24.2",
"zustand": "^5.0.10"
},
"devDependencies": {
"@types/node": "^20",

View file

@ -1,77 +1,59 @@
"use client";
import React, { useState } from "react";
import StudioSidebar from "./components/StudioSidebar";
import StudioEditor from "./components/StudioEditor";
import StudioBottomPanel from "./components/StudioBottomPanel";
import StudioRightPanel from "./components/StudioRightPanel";
import StudioNetworkViz from "./components/StudioNetworkViz";
import TemplatesDrawer from "./components/TemplatesDrawer";
import PreviewModal from "./components/PreviewModal";
import NewProjectModal from "./components/NewProjectModal";
import TranslationPanel from "./components/TranslationPanel";
import PassportLogin from "./components/PassportLogin";
import Toaster from "./components/ui/toaster";
import CommandPalette from "./components/ui/CommandPalette";
import { toast } from "./lib/toast";
import { captureEvent } from "./lib/captureEvent";
import Suspense from "./components/Suspense";
import StudioLayout from "../components/StudioLayout";
function App() {
// TODO: restore all state, handlers, and imports here
// Minimal state stubs
const [user] = useState<any>(null);
const [showPassportLogin, setShowPassportLogin] = useState(false);
const [showNewProject, setShowNewProject] = useState(false);
const [showTemplates, setShowTemplates] = useState(false);
const [showTranslation, setShowTranslation] = useState(false);
const [showFileSearch, setShowFileSearch] = useState(false);
const [showCommandPalette, setShowCommandPalette] = useState(false);
const [consoleCollapsed, setConsoleCollapsed] = useState(false);
const [openFiles, setOpenFiles] = useState<any[]>([]);
const [activeFileId, setActiveFileId] = useState<string>("");
const [code, setCode] = useState<string>("");
const [currentCode, setCurrentCode] = useState<string>("");
const [showPreview, setShowPreview] = useState(false);
// Handler stubs
const handleCodeChange = (c: string) => setCode(c);
const handleFileSelect = (id: string) => setActiveFileId(id);
const handleFileClose = (id: string) => setOpenFiles(files => files.filter(f => f.id !== id));
const handleTemplateSelect = () => {};
const handleFileRename = () => {};
const handleFileDelete = () => {};
const handleFileMove = () => {};
return (
<>
<Toaster position="bottom-right" />
<div className="flex h-screen overflow-hidden bg-background">
<StudioSidebar
user={user}
onLogout={() => setShowPassportLogin(true)}
onNewProject={() => setShowNewProject(true)}
onOpenTemplates={() => setShowTemplates(true)}
onOpenTranslation={() => setShowTranslation(true)}
onFileSearch={() => setShowFileSearch(true)}
onCommandPalette={() => setShowCommandPalette(true)}
consoleCollapsed={consoleCollapsed}
onConsoleToggle={() => setConsoleCollapsed((prev) => !prev)}
/>
<div className="flex-1 flex flex-col overflow-hidden">
<StudioEditor
code={code}
onCodeChange={handleCodeChange}
currentFileId={activeFileId}
onFileSelect={handleFileSelect}
onFileClose={handleFileClose}
openFiles={openFiles}
/>
<StudioBottomPanel
onRun={() => {
toast.success('Code executed!');
captureEvent('run_code');
}}
onStop={() => {
toast.success('Code stopped.');
captureEvent('stop_code');
}}
/>
<StudioRightPanel
fileId={activeFileId}
onFileRename={handleFileRename}
onFileDelete={handleFileDelete}
onFileMove={handleFileMove}
/>
<StudioNetworkViz
data={{}} // TODO: wire up actual network data
onNodeClick={(node) => {
setActiveFileId(node.id);
setCode(node.content);
setCurrentCode(node.content);
}}
/>
</div>
</div>
<Suspense fallback={<div className="fixed inset-0 flex items-center justify-center bg-background/80 z-50">Loading</div>}>
{showTemplates && <TemplatesDrawer onSelect={handleTemplateSelect} onClose={() => setShowTemplates(false)} />}
{showPreview && <PreviewModal code={currentCode} onClose={() => setShowPreview(false)} />}
{showNewProject && <NewProjectModal onClose={() => setShowNewProject(false)} />}
{showTranslation && <TranslationPanel onClose={() => setShowTranslation(false)} />}
{showPassportLogin && <PassportLogin onClose={() => setShowPassportLogin(false)} />}
<StudioLayout>
<Toaster />
<Suspense>
{showTemplates && <TemplatesDrawer onSelectTemplate={handleTemplateSelect} onClose={() => setShowTemplates(false)} currentPlatform={"roblox"} />}
{showPreview && <PreviewModal open={showPreview} code={currentCode} onClose={() => setShowPreview(false)} />}
{showNewProject && <NewProjectModal open={showNewProject} onClose={() => setShowNewProject(false)} onCreateProject={() => {}} />}
{showTranslation && <TranslationPanel isOpen={showTranslation} onClose={() => setShowTranslation(false)} currentCode={currentCode} currentPlatform={"roblox"} />}
{showPassportLogin && <PassportLogin open={showPassportLogin} onClose={() => setShowPassportLogin(false)} onLoginSuccess={() => {}} />}
</Suspense>
<CommandPalette
open={showCommandPalette}
onClose={() => setShowCommandPalette(false)}
commands={createDefaultCommands({
onNewProject: () => setShowNewProject(true),
onTemplates: () => setShowTemplates(true),
onPreview: () => setShowPreview(true),
onExport: () => toast.success('Exported!'),
onCopy: () => toast.success('Copied!'),
})}
/>
</>
<CommandPalette />
</StudioLayout>
);
}

View file

@ -11,7 +11,7 @@ interface ErrorFallbackProps {
export const ErrorFallback = ({ error, resetErrorBoundary }: ErrorFallbackProps) => {
// When encountering an error in the development mode, rethrow it and don't display the boundary.
// The parent UI will take care of showing a more helpful dialog.
if (import.meta.env.DEV) throw error;
if (process.env.NODE_ENV === 'development') throw error;
return (
<div className="min-h-screen bg-background flex items-center justify-center p-4">

View file

@ -1,90 +1,77 @@
'use server';
"use server";
/**
* @fileOverview This file defines a Genkit flow that helps new users by suggesting an initial set of code files
* and project structure based on a simple prompt describing the desired application.
*
* - aiHelpFromPrompt - A function that takes a prompt and returns suggested code files and project structure.
* - AIHelpFromPromptInput - The input type for the aiHelpFromPrompt function.
* - AIHelpFromPromptOutput - The return type for the aiHelpFromPrompt function.
*/
import {ai} from '@/ai/genkit';
import {z} from 'genkit';
import { ai } from "../../ai/genkit";
import { z } from "genkit";
const AIHelpFromPromptInputSchema = z.object({
prompt: z.string().describe('A prompt describing the type of application to build.'),
prompt: z.string().describe("A prompt describing the type of application to build."),
});
export type AIHelpFromPromptInput = z.infer<typeof AIHelpFromPromptInputSchema>;
const AIHelpFromPromptOutputSchema = z.object({
suggestedFiles: z.array(z.object({
filePath: z.string().describe('The path for the suggested file.'),
fileContent: z.string().describe('The content of the suggested file.'),
})).describe('An array of suggested code files and their content.'),
explanation: z.string().describe('An explanation of the suggested file structure and code.'),
filePath: z.string().describe("The path for the suggested file."),
fileContent: z.string().describe("The content of the suggested file."),
})).describe("An array of suggested code files and their content."),
explanation: z.string().describe("An explanation of the suggested file structure and code."),
});
export type AIHelpFromPromptOutput = z.infer<typeof AIHelpFromPromptOutputSchema>;
export async function aiHelpFromPrompt(input: AIHelpFromPromptInput): Promise<AIHelpFromPromptOutput> {
return aiHelpFromPromptFlow(input);
}
const prompt = ai.definePrompt({
name: 'aiHelpFromPromptPrompt',
input: {schema: AIHelpFromPromptInputSchema},
output: {schema: AIHelpFromPromptOutputSchema},
name: "aiHelpFromPromptPrompt",
input: { schema: AIHelpFromPromptInputSchema },
output: { schema: AIHelpFromPromptOutputSchema },
prompt: `You are an AI assistant designed to help new users quickly start developing applications.
Based on the user's prompt describing the desired application, suggest an initial set of code files and a project structure to get them started.
Based on the user's prompt describing the desired application, suggest an initial set of code files and a project structure to get them started.
Provide the suggested files as an array of objects, each containing the file path and the file content.
Explain the suggested file structure and the code in detail so that the user understands the purpose of each file and how they fit together.
Provide the suggested files as an array of objects, each containing the file path and the file content.
Explain the suggested file structure and the code in detail so that the user understands the purpose of each file and how they fit together.
User Prompt: {{{prompt}}}
User Prompt: {{{prompt}}}
Example Output:
{
"suggestedFiles": [
{
"filePath": "src/components/MyComponent.tsx",
"fileContent": "// MyComponent.tsx\nimport React from 'react';\n\nconst MyComponent = () => {\n return (\n <div>\n <h1>Hello, world!</h1>\n </div>\n );\n};\n\nexport default MyComponent;"
},
{
"filePath": "src/pages/index.tsx",
"fileContent": "// index.tsx\nimport MyComponent from '../components/MyComponent';\n\nconst Home = () => {\n return (\n <div>\n <MyComponent />\n </div>\n );\n};\n\nexport default Home;"
}
],
"explanation": "This project structure includes a component (MyComponent.tsx) and a page (index.tsx) that uses the component. This is a basic structure for a React application."
}
`,
Example Output:
{
"suggestedFiles": [
{
"filePath": "src/components/MyComponent.tsx",
"fileContent": "// MyComponent.tsx\nimport React from 'react';\n\nconst MyComponent = () => {\n return (\n <div>\n <h1>Hello, world!</h1>\n </div>\n );\n};\n\nexport default MyComponent;"
},
{
"filePath": "src/pages/index.tsx",
"fileContent": "// index.tsx\nimport MyComponent from '../components/MyComponent';\n\nconst Home = () => {\n return (\n <div>\n <MyComponent />\n </div>\n );\n};\n\nexport default Home;"
}
],
"explanation": "This project structure includes a component (MyComponent.tsx) and a page (index.tsx) that uses the component. This is a basic structure for a React application."
}
`,
});
const aiHelpFromPromptFlow = ai.defineFlow(
{
name: 'aiHelpFromPromptFlow',
name: "aiHelpFromPromptFlow",
inputSchema: AIHelpFromPromptInputSchema,
outputSchema: AIHelpFromPromptOutputSchema,
},
async input => {
const {output} = await prompt(input, {
async (input) => {
const { output } = await prompt(input, {
config: {
safetySettings: [
{
category: 'HARM_CATEGORY_HATE_SPEECH',
threshold: 'BLOCK_ONLY_HIGH',
category: "HARM_CATEGORY_HATE_SPEECH",
threshold: "BLOCK_ONLY_HIGH",
},
{
category: 'HARM_CATEGORY_DANGEROUS_CONTENT',
threshold: 'BLOCK_NONE',
category: "HARM_CATEGORY_DANGEROUS_CONTENT",
threshold: "BLOCK_NONE",
},
{
category: 'HARM_CATEGORY_HARASSMENT',
threshold: 'BLOCK_MEDIUM_AND_ABOVE',
category: "HARM_CATEGORY_HARASSMENT",
threshold: "BLOCK_MEDIUM_AND_ABOVE",
},
{
category: 'HARM_CATEGORY_SEXUALLY_EXPLICIT',
threshold: 'BLOCK_LOW_AND_ABOVE',
category: "HARM_CATEGORY_SEXUALLY_EXPLICIT",
threshold: "BLOCK_LOW_AND_ABOVE",
},
],
},
@ -92,38 +79,7 @@ const aiHelpFromPromptFlow = ai.defineFlow(
return output!;
}
);
'use server';
/**
* @fileOverview This file defines a Genkit flow that helps new users by suggesting an initial set of code files
* and project structure based on a simple prompt describing the desired application.
*
* - aiHelpFromPrompt - A function that takes a prompt and returns suggested code files and project structure.
* - AIHelpFromPromptInput - The input type for the aiHelpFromPrompt function.
* - AIHelpFromPromptOutput - The return type for the aiHelpFromPrompt function.
*/
import {ai} from '@/ai/genkit';
import {z} from 'genkit';
const AIHelpFromPromptInputSchema = z.object({
prompt: z.string().describe('A prompt describing the type of application to build.'),
});
export type AIHelpFromPromptInput = z.infer<typeof AIHelpFromPromptInputSchema>;
const AIHelpFromPromptOutputSchema = z.object({
suggestedFiles: z.array(z.object({
filePath: z.string().describe('The path for the suggested file.'),
fileContent: z.string().describe('The content of the suggested file.'),
})).describe('An array of suggested code files and their content.'),
explanation: z.string().describe('An explanation of the suggested file structure and code.'),
});
export type AIHelpFromPromptOutput = z.infer<typeof AIHelpFromPromptOutputSchema>;
export async function aiHelpFromPrompt(input: AIHelpFromPromptInput): Promise<AIHelpFromPromptOutput> {
return aiHelpFromPromptFlow(input);
}
const prompt = ai.definePrompt({
name: 'aiHelpFromPromptPrompt',
input: {schema: AIHelpFromPromptInputSchema},

View file

@ -1,15 +1,5 @@
'use server';
/**
* @fileOverview An AI agent that detects synchronization conflicts between platform-specific code and data,
* and suggests solutions to resolve them.
*
* - aiSuggestedSyncConflictResolution - A function that handles the conflict resolution process.
* - AISuggestedSyncConflictResolutionInput - The input type for the aiSuggestedSyncConflictResolution function.
* - AISuggestedSyncConflictResolutionOutput - The return type for the aiSuggestedSyncConflictResolution function.
*/
import {ai} from '@/ai/genkit';
import {ai} from '../../ai/genkit';
import {z} from 'genkit';
const AISuggestedSyncConflictResolutionInputSchema = z.object({
@ -27,10 +17,6 @@ const AISuggestedSyncConflictResolutionOutputSchema = z.object({
});
export type AISuggestedSyncConflictResolutionOutput = z.infer<typeof AISuggestedSyncConflictResolutionOutputSchema>;
export async function aiSuggestedSyncConflictResolution(input: AISuggestedSyncConflictResolutionInput): Promise<AISuggestedSyncConflictResolutionOutput> {
return aiSuggestedSyncConflictResolutionFlow(input);
}
const prompt = ai.definePrompt({
name: 'aiSuggestedSyncConflictResolutionPrompt',
input: {schema: AISuggestedSyncConflictResolutionInputSchema},
@ -60,60 +46,11 @@ const aiSuggestedSyncConflictResolutionFlow = ai.defineFlow(
inputSchema: AISuggestedSyncConflictResolutionInputSchema,
outputSchema: AISuggestedSyncConflictResolutionOutputSchema,
},
async input => {
const {output} = await prompt(input, {
config: {
safetySettings: [
{
category: 'HARM_CATEGORY_HATE_SPEECH',
threshold: 'BLOCK_ONLY_HIGH',
},
{
category: 'HARM_CATEGORY_DANGEROUS_CONTENT',
threshold: 'BLOCK_NONE',
},
{
category: 'HARM_CATEGORY_HARASSMENT',
threshold: 'BLOCK_MEDIUM_AND_ABOVE',
},
{
category: 'HARM_CATEGORY_SEXUALLY_EXPLICIT',
threshold: 'BLOCK_LOW_AND_ABOVE',
},
],
},
});
async (input: AISuggestedSyncConflictResolutionInput) => {
const {output} = await prompt(input);
return output!;
}
);
'use server';
/**
* @fileOverview An AI agent that detects synchronization conflicts between platform-specific code and data,
* and suggests solutions to resolve them.
*
* - aiSuggestedSyncConflictResolution - A function that handles the conflict resolution process.
* - AISuggestedSyncConflictResolutionInput - The input type for the aiSuggestedSyncConflictResolution function.
* - AISuggestedSyncConflictResolutionOutput - The return type for the aiSuggestedSyncConflictResolution function.
*/
import {ai} from '@/ai/genkit';
import {z} from 'genkit';
const AISuggestedSyncConflictResolutionInputSchema = z.object({
robloxCode: z.string().describe('The Lua code for the Roblox platform.'),
webCode: z.string().describe('The JavaScript code for the web platform.'),
mobileCode: z.string().describe('The React Native code for the mobile platform.'),
sharedState: z.string().describe('The shared state data in JSON format.'),
});
export type AISuggestedSyncConflictResolutionInput = z.infer<typeof AISuggestedSyncConflictResolutionInputSchema>;
const AISuggestedSyncConflictResolutionOutputSchema = z.object({
conflictDetected: z.boolean().describe('Whether a synchronization conflict was detected.'),
suggestedSolutions: z.array(z.string()).describe('An array of suggested solutions to resolve the conflicts.'),
explanation: z.string().describe('Explanation of the detected conflicts and suggested solutions.'),
});
export type AISuggestedSyncConflictResolutionOutput = z.infer<typeof AISuggestedSyncConflictResolutionOutputSchema>;
export async function aiSuggestedSyncConflictResolution(input: AISuggestedSyncConflictResolutionInput): Promise<AISuggestedSyncConflictResolutionOutput> {
return aiSuggestedSyncConflictResolutionFlow(input);

View file

@ -8,7 +8,7 @@
* - ContextualCodeSuggestionsOutput - The return type for the contextualCodeSuggestions function.
*/
import {ai} from '@/ai/genkit';
import {ai} from '../../ai/genkit';
import {z} from 'genkit';
const ContextualCodeSuggestionsInputSchema = z.object({
@ -69,43 +69,9 @@ const contextualCodeSuggestionsFlow = ai.defineFlow(
inputSchema: ContextualCodeSuggestionsInputSchema,
outputSchema: ContextualCodeSuggestionsOutputSchema,
},
async input => {
async (input: ContextualCodeSuggestionsInput) => {
const {output} = await prompt(input);
return output!;
}
);
'use server';
/**
* @fileOverview This file defines a Genkit flow for providing contextual code suggestions.
*
* - contextualCodeSuggestions - A function that takes the current file content and cursor position
* and returns code suggestions.
* - ContextualCodeSuggestionsInput - The input type for the contextualCodeSuggestions function.
* - ContextualCodeSuggestionsOutput - The return type for the contextualCodeSuggestions function.
*/
import {ai} from '@/ai/genkit';
import {z} from 'genkit';
const ContextualCodeSuggestionsInputSchema = z.object({
fileContent: z.string().describe('The content of the currently open file.'),
cursorPosition: z.number().describe('The cursor position within the file.'),
language: z.string().describe('The programming language of the file.'),
context: z.string().optional().describe('Additional context for code suggestions, e.g., error messages or related code snippets.'),
});
export type ContextualCodeSuggestionsInput = z.infer<
typeof ContextualCodeSuggestionsInputSchema
>;
const ContextualCodeSuggestionsOutputSchema = z.object({
suggestions: z
.array(z.string())
.describe('An array of code suggestions based on the context.'),
});
export type ContextualCodeSuggestionsOutput = z.infer<
typeof ContextualCodeSuggestionsOutputSchema
>;
export async function contextualCodeSuggestions(
input: ContextualCodeSuggestionsInput
): Promise<ContextualCodeSuggestionsOutput> {

View file

@ -1,4 +1,4 @@
import { DashboardPage } from "@/components/aethex/dashboard-page";
import { DashboardPage } from "../../components/aethex/dashboard-page";
export default function Page() {
return <DashboardPage />;

34
src/app/landing-page.tsx Normal file
View file

@ -0,0 +1,34 @@
import Link from "next/link";
export default function LandingPage() {
return (
<main className="min-h-screen flex flex-col items-center justify-center bg-gradient-to-br from-gray-900 via-gray-800 to-gray-950 text-white p-8">
<div className="max-w-xl w-full flex flex-col items-center">
<img src="/logo.svg" alt="AeThex Studio Logo" className="w-24 h-24 mb-6" />
<h1 className="text-4xl font-bold mb-4 text-center">Welcome to AeThex Studio</h1>
<p className="text-lg mb-8 text-center opacity-80">
The Next-Generation Cross-Platform IDE for Creators, Developers, and Teams.
</p>
<div className="flex flex-col gap-4 w-full">
<a
href="https://aethex.com/download"
className="bg-blue-600 hover:bg-blue-700 text-white font-semibold py-3 rounded-lg text-center transition-colors shadow-lg"
target="_blank"
rel="noopener noreferrer"
>
Download AeThex Studio
</a>
<Link
href="/ide"
className="bg-gray-800 hover:bg-gray-700 text-white font-semibold py-3 rounded-lg text-center transition-colors border border-gray-700"
>
Open in Browser
</Link>
</div>
<p className="mt-8 text-sm text-gray-400 text-center">
Need help? <a href="https://aethex.com/docs" className="underline hover:text-blue-400">Read the Docs</a>
</p>
</div>
</main>
);
}

View file

@ -1,5 +1,5 @@
import type { Metadata } from "next";
import { Toaster } from "@/components/ui/toaster";
import Toaster from "../components/ui/toaster";
import "./globals.css";
export const metadata: Metadata = {

View file

@ -1,5 +1,34 @@
import { LoginPage } from "@/components/aethex/login-page";
export default function Page() {
return <LoginPage />;
return (
<main className="min-h-screen flex flex-col items-center justify-center bg-gradient-to-br from-gray-950 to-gray-900 text-white">
<div className="max-w-xl w-full px-6 py-12 rounded-xl shadow-2xl bg-black/70 border border-gray-800 flex flex-col items-center">
<h1 className="text-4xl font-bold mb-4 tracking-tight">Welcome to AeThex Studio</h1>
<p className="mb-6 text-lg text-gray-300 text-center">
The next-generation cross-platform IDE for creators, educators, and teams.<br />
Download or launch AeThex Studio below.
</p>
<div className="flex flex-col gap-4 w-full items-center">
<a
href="/ide"
className="w-full py-3 px-6 rounded-lg bg-blue-600 hover:bg-blue-700 text-white font-semibold text-lg text-center transition"
>
Launch Web IDE
</a>
<a
href="https://github.com/AeThex-LABS/aethex-studio/releases/latest"
target="_blank"
rel="noopener noreferrer"
className="w-full py-3 px-6 rounded-lg bg-gray-800 hover:bg-gray-700 text-gray-100 font-semibold text-lg text-center border border-gray-700 transition"
>
Download Desktop App
</a>
</div>
<p className="mt-8 text-xs text-gray-500 text-center">
Open source on <a href="https://github.com/AeThex-LABS/aethex-studio" className="underline hover:text-blue-400">GitHub</a>
</p>
</div>
</main>
);
}

View file

@ -0,0 +1 @@
export default function StudioBottomPanel() { return <footer>Bottom Panel</footer>; }

View file

@ -0,0 +1 @@
export default function StudioEditor() { return <div>Editor</div>; }

View file

@ -0,0 +1 @@
export default function StudioNetworkViz() { return <div>Network Viz</div>; }

View file

@ -0,0 +1 @@
export default function StudioRightPanel() { return <aside>Right Panel</aside>; }

View file

@ -0,0 +1 @@
export default function StudioSidebar() { return <aside>Sidebar</aside>; }

View file

@ -0,0 +1 @@
export default function Suspense({ children }: { children: React.ReactNode }) { return <>{children}</>; }

View file

@ -0,0 +1,2 @@
// TypeScript declaration for AethexStudio module
export * from "./aethex-studio";

View file

@ -1,3 +1,4 @@
"use client";
import { useState } from "react";
@ -5,35 +6,28 @@ import {
ResizableHandle,
ResizablePanel,
ResizablePanelGroup,
} from "@/components/ui/resizable";
} from "../ui/resizable";
import { Navbar } from "./navbar";
import { FileNavigator } from "./file-navigator";
import FileNavigator from "./file-navigator";
import { MainView } from "./main-view";
import { BottomPanel } from "./bottom-panel";
import { AiAssistant } from "./ai-assistant";
import AiAssistant from "./ai-assistant";
import {
openFiles as initialOpenFiles,
fileTree as initialFileTree,
File as OpenFileType,
initialFileTree,
File,
FolderNode,
FileNode,
} from "@/lib/aethex-data";
} from "../../lib/aethex-data";
import { NewProjectModal } from "./new-project-modal";
import {
ProjectTemplate,
generateFileContent,
} from "@/lib/templates";
import type { NewProjectFormValues } from "./new-project-modal";
export type { OpenFileType };
import { ScriptTemplate, getTemplatesForPlatform } from "../../lib/templates";
export function AethexStudio() {
const [openFiles, setOpenFiles] = useState<OpenFileType[]>(initialOpenFiles);
const [openFiles, setOpenFiles] = useState<File[]>([]);
const [activeTab, setActiveTab] = useState<string>(openFiles[0]?.id || "");
const [fileTree, setFileTree] = useState<FolderNode>(initialFileTree);
const [isNewProjectModalOpen, setIsNewProjectModalOpen] = useState(false);
const handleOpenFile = (file: OpenFileType) => {
const handleOpenFile = (file: File) => {
if (!openFiles.find((f) => f.id === file.id)) {
setOpenFiles((prev) => [...prev, file]);
}
@ -53,57 +47,24 @@ export function AethexStudio() {
}
};
const handleCreateProject = (
template: ProjectTemplate,
config: NewProjectFormValues
) => {
const newFileTree: FolderNode = {
...template.fileTree,
name: config.projectName,
const handleCreateProject = (name: string) => {
const newFile: File = {
id: name + Date.now(),
name,
language: "lua",
content: "",
};
setFileTree(newFileTree);
let mainFileToOpen: OpenFileType | undefined;
const findMainFile = (node: FolderNode | FileNode, currentPath: string) => {
if (mainFileToOpen) return;
const newPath = currentPath ? `${currentPath}/${node.name}` : node.name;
if (node.type === "file" && node.name === template.mainFile) {
mainFileToOpen = {
id: newPath,
name: node.name,
content: generateFileContent(node, template),
};
} else if (node.type === "folder" && node.children) {
node.children.forEach((child) => findMainFile(child, newPath));
}
};
findMainFile(newFileTree, "");
if (mainFileToOpen) {
setOpenFiles([mainFileToOpen]);
setActiveTab(mainFileToOpen.id);
}
setOpenFiles([...openFiles, newFile]);
};
return (
<div className="aethex-studio">
{/* ...rest of the studio UI... */}
<Navbar />
<FileNavigator fileTree={fileTree} onOpenFile={handleOpenFile} />
<MainView
openFiles={openFiles}
activeTab={activeTab}
onCloseFile={handleCloseFile}
onOpenFile={handleOpenFile}
/>
<MainView />
<BottomPanel />
<AiAssistant />
<NewProjectModal
open={isNewProjectModalOpen}
onOpenChange={setIsNewProjectModalOpen}
onCreate={handleCreateProject}
/>
<NewProjectModal onCreate={handleCreateProject} onClose={() => setIsNewProjectModalOpen(false)} />
</div>
);
}

View file

@ -37,4 +37,9 @@ import {
TooltipTrigger,
} from "@/components/ui/tooltip";
// ...rest of the AiAssistant code from backup...
const AiAssistant = () => {
return <aside>AiAssistant (stub)</aside>;
};
export default AiAssistant;

View file

@ -18,7 +18,14 @@ import { Input } from "@/components/ui/input";
import { FileNode, FolderNode, File as OpenFileType } from "@/lib/aethex-data";
import { generateFileContent } from "@/lib/templates";
type FileNavigatorProps = {
onOpenFile: (file: OpenFileType) => void;
fileTree: FolderNode;
};
const FileNavigator: React.FC<FileNavigatorProps> = () => {
return <nav>FileNavigator (stub)</nav>;
};
export default FileNavigator;

View file

@ -0,0 +1 @@
export { AethexStudio } from "./aethex-studio";

View file

@ -0,0 +1 @@
export { AethexStudio } from "./aethex-studio";

View file

@ -2,14 +2,14 @@
import Link from "next/link";
import Image from "next/image";
import { AethexLogo, GoogleIcon } from "@/components/aethex/icons";
import { AethexLogo, GoogleIcon } from "./icons";
import { Button } from "@/components/ui/button";
import { Github } from "lucide-react";
import { PlaceHolderImages } from "@/lib/placeholder-images";
import { PlaceHolderImages } from "../../lib/placeholder-images";
export function LoginPage() {
const loginIllustration = PlaceHolderImages.find(
(p) => p.id === "login-illustration"
(p: { id: string }) => p.id === "login-illustration"
);
return (

View file

@ -0,0 +1 @@
export function MainView() { return <main>Main View (stub)</main>; }

View file

@ -0,0 +1 @@
export function Navbar() { return <nav>Navbar (stub)</nav>; }

View file

@ -1,14 +1,21 @@
import React from "react";
import { RobloxIcon, WebIcon, MobileIcon } from "./icons";
interface Workspace {
id: string;
name: string;
lastModified: string;
platforms: React.ComponentType[];
platforms: string[];
thumbnailUrlId: string;
thumbnailImageHint: string;
}
const platformIconMap: Record<string, React.FC<React.SVGProps<SVGSVGElement>>> = {
roblox: RobloxIcon,
web: WebIcon,
mobile: MobileIcon,
};
export function WorkspaceCard({ workspace }: { workspace: Workspace }) {
return (
<div className="rounded-lg border bg-card p-4 shadow">
@ -18,9 +25,10 @@ export function WorkspaceCard({ workspace }: { workspace: Workspace }) {
<div className="mt-4 font-semibold text-lg">{workspace.name}</div>
<div className="mt-2 text-xs text-muted-foreground">{workspace.lastModified}</div>
<div className="mt-2 flex gap-2">
{workspace.platforms.map((PlatformIcon, i) => (
<PlatformIcon key={i} className="h-4 w-4" />
))}
{workspace.platforms.map((platform, i) => {
const Icon = platformIconMap[platform];
return Icon ? <Icon key={i} className="h-4 w-4" /> : null;
})}
</div>
</div>
);

View file

@ -0,0 +1 @@
export default function CommandPalette() { return <div>Command Palette</div>; }

View file

@ -2,7 +2,7 @@
import { ComponentProps } from "react"
import * as CheckboxPrimitive from "@radix-ui/react-checkbox"
import CheckIcon from "lucide-react/dist/esm/icons/check"
import { Check } from "lucide-react";
import { cn } from "@/lib/utils"
@ -23,7 +23,7 @@ function Checkbox({
data-slot="checkbox-indicator"
className="flex items-center justify-center text-current transition-none"
>
<CheckIcon className="size-3.5" />
<Check className="size-3.5" />
</CheckboxPrimitive.Indicator>
</CheckboxPrimitive.Root>
)

View file

@ -1,6 +1,6 @@
import { ComponentProps } from "react"
import * as DialogPrimitive from "@radix-ui/react-dialog"
import XIcon from "lucide-react/dist/esm/icons/x"
import { X } from "lucide-react";
import { cn } from "@/lib/utils"
@ -62,7 +62,7 @@ function DialogContent({
>
{children}
<DialogPrimitive.Close className="ring-offset-background focus:ring-ring data-[state=open]:bg-accent data-[state=open]:text-muted-foreground absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4">
<XIcon />
<X />
<span className="sr-only">Close</span>
</DialogPrimitive.Close>
</DialogPrimitive.Content>

View file

@ -1,8 +1,6 @@
import { ComponentProps } from "react"
import * as SelectPrimitive from "@radix-ui/react-select"
import CheckIcon from "lucide-react/dist/esm/icons/check"
import ChevronDownIcon from "lucide-react/dist/esm/icons/chevron-down"
import ChevronUpIcon from "lucide-react/dist/esm/icons/chevron-up"
import { Check, ChevronDown, ChevronUp } from "lucide-react";
import { cn } from "@/lib/utils"
@ -44,7 +42,7 @@ function SelectTrigger({
>
{children}
<SelectPrimitive.Icon asChild>
<ChevronDownIcon className="size-4 opacity-50" />
<ChevronDown className="size-4 opacity-50" />
</SelectPrimitive.Icon>
</SelectPrimitive.Trigger>
)
@ -114,7 +112,7 @@ function SelectItem({
>
<span className="absolute right-2 flex size-3.5 items-center justify-center">
<SelectPrimitive.ItemIndicator>
<CheckIcon className="size-4" />
<Check className="size-4" />
</SelectPrimitive.ItemIndicator>
</span>
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
@ -148,7 +146,7 @@ function SelectScrollUpButton({
)}
{...props}
>
<ChevronUpIcon className="size-4" />
<ChevronUp className="size-4" />
</SelectPrimitive.ScrollUpButton>
)
}
@ -166,7 +164,7 @@ function SelectScrollDownButton({
)}
{...props}
>
<ChevronDownIcon className="size-4" />
<ChevronDown className="size-4" />
</SelectPrimitive.ScrollDownButton>
)
}

View file

@ -1,6 +1,6 @@
import { ComponentProps } from "react"
import * as SheetPrimitive from "@radix-ui/react-dialog"
import XIcon from "lucide-react/dist/esm/icons/x"
import { X } from "lucide-react"
import { cn } from "@/lib/utils"
@ -71,7 +71,7 @@ function SheetContent({
>
{children}
<SheetPrimitive.Close className="ring-offset-background focus:ring-ring data-[state=open]:bg-secondary absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none">
<XIcon className="size-4" />
<X className="size-4" />
<span className="sr-only">Close</span>
</SheetPrimitive.Close>
</SheetPrimitive.Content>

View file

@ -0,0 +1,3 @@
export default function Toaster() {
return <div>Toaster (stub)</div>;
}

11
src/hooks/use-toast.ts Normal file
View file

@ -0,0 +1,11 @@
import { useCallback } from "react";
export function useToast() {
// Simple stub for toast notifications
return {
toast: useCallback((opts: { title: string; description?: string }) => {
// You can replace this with a real toast implementation
alert(`${opts.title}${opts.description ? ': ' + opts.description : ''}`);
}, []),
};
}

1
src/lib/captureEvent.ts Normal file
View file

@ -0,0 +1 @@
export function captureEvent(event: string) { console.log('Event:', event); }

View file

@ -2,13 +2,13 @@ import { PlatformId } from './platforms';
import { uefnTemplates } from './templates-uefn';
import { spatialTemplates } from './templates-spatial';
export interface ScriptTemplate {
id: string;
name: string;
description: string;
code: string;
category: 'beginner' | 'gameplay' | 'ui' | 'tools' | 'advanced';
platform: PlatformId;
id: string;
name: string;
description: string;
code: string;
category: 'beginner' | 'gameplay' | 'ui' | 'tools' | 'advanced';
platform: PlatformId;
badge?: string;
}

1
src/lib/toast.ts Normal file
View file

@ -0,0 +1 @@
export const toast = { success: (msg: string) => { console.log('Toast:', msg); } }

View file

@ -1,6 +1,48 @@
import { clsx, type ClassValue } from "clsx"
import { twMerge } from "tailwind-merge"
import { clsx, type ClassValue } from "clsx";
import { twMerge } from "tailwind-merge";
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
return twMerge(clsx(inputs));
}
export function formatTimestamp(date: Date): string {
const hours = date.getHours().toString().padStart(2, '0');
const minutes = date.getMinutes().toString().padStart(2, '0');
const seconds = date.getSeconds().toString().padStart(2, '0');
const ms = date.getMilliseconds().toString().padStart(3, '0');
return `${hours}:${minutes}:${seconds}.${ms}`;
}
export function getFileIcon(fileName: string): string {
if (fileName.endsWith('.lua')) return '🎮';
if (fileName.endsWith('.js') || fileName.endsWith('.jsx')) return '🌐';
if (fileName.endsWith('.ts') || fileName.endsWith('.tsx')) return '📘';
if (fileName.endsWith('.html')) return '📄';
if (fileName.endsWith('.css')) return '🎨';
if (fileName.endsWith('.json')) return '📋';
if (fileName.endsWith('.md')) return '📝';
return '📄';
}
export function getPlatformIcon(platform: string): string {
switch (platform) {
case 'roblox': return '🎮';
case 'web': return '🌐';
case 'mobile': return '📱';
case 'desktop': return '🖥️';
case 'shared': return '🔗';
default: return '📄';
}
}
export function getPlatformColor(platform: string): string {
switch (platform) {
case 'roblox': return 'text-red-400';
case 'web': return 'text-blue-400';
case 'mobile': return 'text-green-400';
case 'desktop': return 'text-purple-400';
case 'system': return 'text-gray-400';
default: return 'text-gray-400';
}
}

1
src/lib/utils/index.ts Normal file
View file

@ -0,0 +1 @@
export * from "./utils";

View file

@ -17,17 +17,17 @@ interface EditorState {
updateFileContent: (id: string, content: string) => void;
}
export const useEditorStore = create<EditorState>((set, get) => ({
export const useEditorStore = create<EditorState>((set: any, get: any) => ({
openTabs: [],
activeTabId: null,
files: [
{ id: '1', name: 'main.lua', content: '-- Main Lua file\nprint("Hello, AeThex!")' },
{ id: '2', name: 'utils.lua', content: '-- Utility functions\nfunction greet(name)\n print("Hello, " .. name)\nend' },
],
openFile: (file) => {
openFile: (file: FileTab) => {
const { openTabs } = get();
if (!openTabs.find((f) => f.id === file.id)) {
set((state) => ({
if (!openTabs.find((f: FileTab) => f.id === file.id)) {
set((state: EditorState) => ({
openTabs: [...state.openTabs, file],
activeTabId: file.id,
}));
@ -35,9 +35,9 @@ export const useEditorStore = create<EditorState>((set, get) => ({
set({ activeTabId: file.id });
}
},
closeTab: (id) => {
set((state) => {
const newTabs = state.openTabs.filter((f) => f.id !== id);
closeTab: (id: string) => {
set((state: EditorState) => {
const newTabs = state.openTabs.filter((f: FileTab) => f.id !== id);
let newActive = state.activeTabId;
if (state.activeTabId === id) {
newActive = newTabs.length > 0 ? newTabs[newTabs.length - 1].id : null;
@ -45,12 +45,12 @@ export const useEditorStore = create<EditorState>((set, get) => ({
return { openTabs: newTabs, activeTabId: newActive };
});
},
setActiveTab: (id) => set({ activeTabId: id }),
setFiles: (files) => set({ files }),
updateFileContent: (id, content) => {
set((state) => ({
files: state.files.map((f) => f.id === id ? { ...f, content } : f),
openTabs: state.openTabs.map((f) => f.id === id ? { ...f, content } : f),
setActiveTab: (id: string) => set({ activeTabId: id }),
setFiles: (files: FileTab[]) => set({ files }),
updateFileContent: (id: string, content: string) => {
set((state: EditorState) => ({
files: state.files.map((f: FileTab) => f.id === id ? { ...f, content } : f),
openTabs: state.openTabs.map((f: FileTab) => f.id === id ? { ...f, content } : f),
}));
},
}));

View file

@ -20,7 +20,10 @@
}
],
"paths": {
"@/*": ["./*"]
"@/*": ["src/*"],
"@/store/*": ["store/*"],
"@/lib/*": ["src/lib/*"],
"@/hooks/*": ["src/hooks/*"]
}
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],