mirror of
https://github.com/jlengrand/Maestro.git
synced 2026-03-10 08:31:19 +00:00
MAESTRO: Create separate Vite config for web interface build
- Add vite.config.web.mts with dedicated web interface configuration - Configure build output to dist/web/ directory - Set up code splitting with React in separate chunk - Enable source maps for development debugging - Add dev server proxy for API and WebSocket connections - Create index.html entry point with mobile-optimized meta tags - Create main.tsx with device detection for mobile/desktop routing - Add index.css with Tailwind, CSS custom properties, and animations - Create placeholder mobile/desktop App components for Phase 1/2 - Update tailwind.config.mjs to include src/web files
This commit is contained in:
151
src/web/desktop/App.tsx
Normal file
151
src/web/desktop/App.tsx
Normal file
@@ -0,0 +1,151 @@
|
|||||||
|
/**
|
||||||
|
* Maestro Desktop Web App
|
||||||
|
*
|
||||||
|
* Full-featured collaborative interface for hackathons and team coding.
|
||||||
|
* Provides real-time collaboration, full visibility, and shared editing.
|
||||||
|
*
|
||||||
|
* Phase 2 implementation will expand this component.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import { useThemeColors } from '../components/ThemeProvider';
|
||||||
|
|
||||||
|
export default function DesktopApp() {
|
||||||
|
const colors = useThemeColors();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
height: '100vh',
|
||||||
|
backgroundColor: colors.background,
|
||||||
|
color: colors.textMain,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* Header */}
|
||||||
|
<header
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
padding: '12px 24px',
|
||||||
|
borderBottom: `1px solid ${colors.border}`,
|
||||||
|
backgroundColor: colors.surface,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<h1 style={{ fontSize: '20px', fontWeight: 600 }}>
|
||||||
|
Maestro Web
|
||||||
|
</h1>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: '12px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: '8px',
|
||||||
|
fontSize: '14px',
|
||||||
|
color: colors.textMuted,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
width: '10px',
|
||||||
|
height: '10px',
|
||||||
|
borderRadius: '50%',
|
||||||
|
backgroundColor: colors.warning,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
Connecting...
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
{/* Main content */}
|
||||||
|
<main
|
||||||
|
style={{
|
||||||
|
flex: 1,
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
padding: '40px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
textAlign: 'center',
|
||||||
|
maxWidth: '600px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
marginBottom: '32px',
|
||||||
|
padding: '32px',
|
||||||
|
borderRadius: '16px',
|
||||||
|
backgroundColor: colors.surface,
|
||||||
|
border: `1px solid ${colors.border}`,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<h2 style={{ fontSize: '24px', marginBottom: '16px' }}>
|
||||||
|
Desktop Collaborative Interface
|
||||||
|
</h2>
|
||||||
|
<p
|
||||||
|
style={{
|
||||||
|
fontSize: '16px',
|
||||||
|
color: colors.textMuted,
|
||||||
|
lineHeight: 1.6,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Full-featured web interface for team collaboration during
|
||||||
|
hackathons and pair programming sessions. Share your AI coding
|
||||||
|
sessions with teammates in real-time.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'grid',
|
||||||
|
gridTemplateColumns: 'repeat(3, 1fr)',
|
||||||
|
gap: '16px',
|
||||||
|
marginBottom: '32px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{[
|
||||||
|
{ title: 'Real-time Sync', desc: 'See changes instantly' },
|
||||||
|
{ title: 'Multi-user', desc: 'Collaborate together' },
|
||||||
|
{ title: 'Full History', desc: 'Complete session logs' },
|
||||||
|
].map(({ title, desc }) => (
|
||||||
|
<div
|
||||||
|
key={title}
|
||||||
|
style={{
|
||||||
|
padding: '16px',
|
||||||
|
borderRadius: '8px',
|
||||||
|
backgroundColor: colors.surface,
|
||||||
|
border: `1px solid ${colors.border}`,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<h3 style={{ fontSize: '14px', marginBottom: '4px' }}>
|
||||||
|
{title}
|
||||||
|
</h3>
|
||||||
|
<p style={{ fontSize: '12px', color: colors.textMuted }}>
|
||||||
|
{desc}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p style={{ fontSize: '14px', color: colors.textMuted }}>
|
||||||
|
This interface will be implemented in Phase 2.
|
||||||
|
<br />
|
||||||
|
Make sure Maestro desktop app is running to connect.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
279
src/web/index.css
Normal file
279
src/web/index.css
Normal file
@@ -0,0 +1,279 @@
|
|||||||
|
/**
|
||||||
|
* Maestro Web Interface Styles
|
||||||
|
*
|
||||||
|
* Base styles and CSS custom properties for the web interface.
|
||||||
|
* Theme colors are injected via JavaScript from ThemeProvider.
|
||||||
|
*/
|
||||||
|
|
||||||
|
@tailwind base;
|
||||||
|
@tailwind components;
|
||||||
|
@tailwind utilities;
|
||||||
|
|
||||||
|
/* CSS Custom Properties - Default values (overridden by theme) */
|
||||||
|
:root {
|
||||||
|
/* Colors */
|
||||||
|
--color-background: #1a1a2e;
|
||||||
|
--color-surface: #16213e;
|
||||||
|
--color-surface-elevated: #1f2e54;
|
||||||
|
--color-border: #2d3a5a;
|
||||||
|
--color-text-main: #eaeaea;
|
||||||
|
--color-text-muted: #8892b0;
|
||||||
|
--color-accent: #4a9eff;
|
||||||
|
--color-accent-hover: #3a8eef;
|
||||||
|
--color-success: #22c55e;
|
||||||
|
--color-warning: #f59e0b;
|
||||||
|
--color-error: #ef4444;
|
||||||
|
|
||||||
|
/* Typography */
|
||||||
|
--font-mono: 'JetBrains Mono', 'Fira Code', 'Courier New', monospace;
|
||||||
|
|
||||||
|
/* Spacing */
|
||||||
|
--spacing-xs: 4px;
|
||||||
|
--spacing-sm: 8px;
|
||||||
|
--spacing-md: 16px;
|
||||||
|
--spacing-lg: 24px;
|
||||||
|
--spacing-xl: 32px;
|
||||||
|
|
||||||
|
/* Border Radius */
|
||||||
|
--radius-sm: 4px;
|
||||||
|
--radius-md: 8px;
|
||||||
|
--radius-lg: 12px;
|
||||||
|
--radius-full: 9999px;
|
||||||
|
|
||||||
|
/* Transitions */
|
||||||
|
--transition-fast: 150ms ease;
|
||||||
|
--transition-normal: 200ms ease;
|
||||||
|
--transition-slow: 300ms ease;
|
||||||
|
|
||||||
|
/* Z-Index layers */
|
||||||
|
--z-dropdown: 100;
|
||||||
|
--z-sticky: 200;
|
||||||
|
--z-modal-backdrop: 300;
|
||||||
|
--z-modal: 400;
|
||||||
|
--z-toast: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Base styles */
|
||||||
|
html {
|
||||||
|
font-size: 16px;
|
||||||
|
-webkit-text-size-adjust: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
background-color: var(--color-background);
|
||||||
|
color: var(--color-text-main);
|
||||||
|
line-height: 1.5;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Selection styling */
|
||||||
|
::selection {
|
||||||
|
background-color: var(--color-accent);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Scrollbar styling */
|
||||||
|
::-webkit-scrollbar {
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-track {
|
||||||
|
background: var(--color-surface);
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-thumb {
|
||||||
|
background: var(--color-border);
|
||||||
|
border-radius: var(--radius-full);
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: var(--color-text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Focus styles */
|
||||||
|
:focus-visible {
|
||||||
|
outline: 2px solid var(--color-accent);
|
||||||
|
outline-offset: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Remove default focus outline (we use focus-visible) */
|
||||||
|
:focus:not(:focus-visible) {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Link styles */
|
||||||
|
a {
|
||||||
|
color: var(--color-accent);
|
||||||
|
text-decoration: none;
|
||||||
|
transition: color var(--transition-fast);
|
||||||
|
}
|
||||||
|
|
||||||
|
a:hover {
|
||||||
|
color: var(--color-accent-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Button reset */
|
||||||
|
button {
|
||||||
|
font-family: inherit;
|
||||||
|
font-size: inherit;
|
||||||
|
cursor: pointer;
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Input reset */
|
||||||
|
input,
|
||||||
|
textarea,
|
||||||
|
select {
|
||||||
|
font-family: inherit;
|
||||||
|
font-size: inherit;
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Mobile-specific styles */
|
||||||
|
@media (max-width: 767px) {
|
||||||
|
/* Prevent text size adjustment */
|
||||||
|
html {
|
||||||
|
-webkit-text-size-adjust: none;
|
||||||
|
text-size-adjust: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Larger tap targets */
|
||||||
|
button,
|
||||||
|
a,
|
||||||
|
input,
|
||||||
|
select,
|
||||||
|
textarea {
|
||||||
|
min-height: 44px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Disable pull-to-refresh on body (we'll handle it ourselves) */
|
||||||
|
body {
|
||||||
|
overscroll-behavior-y: contain;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Safe area insets for iOS */
|
||||||
|
@supports (padding-top: env(safe-area-inset-top)) {
|
||||||
|
.safe-area-top {
|
||||||
|
padding-top: env(safe-area-inset-top);
|
||||||
|
}
|
||||||
|
|
||||||
|
.safe-area-bottom {
|
||||||
|
padding-bottom: env(safe-area-inset-bottom);
|
||||||
|
}
|
||||||
|
|
||||||
|
.safe-area-left {
|
||||||
|
padding-left: env(safe-area-inset-left);
|
||||||
|
}
|
||||||
|
|
||||||
|
.safe-area-right {
|
||||||
|
padding-right: env(safe-area-inset-right);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Animation keyframes */
|
||||||
|
@keyframes spin {
|
||||||
|
to {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes pulse {
|
||||||
|
0%,
|
||||||
|
100% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadeIn {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes slideUp {
|
||||||
|
from {
|
||||||
|
transform: translateY(100%);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
transform: translateY(0);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes slideDown {
|
||||||
|
from {
|
||||||
|
transform: translateY(-100%);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
transform: translateY(0);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Utility classes */
|
||||||
|
.animate-spin {
|
||||||
|
animation: spin 1s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.animate-pulse {
|
||||||
|
animation: pulse 2s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.animate-fadeIn {
|
||||||
|
animation: fadeIn var(--transition-normal);
|
||||||
|
}
|
||||||
|
|
||||||
|
.animate-slideUp {
|
||||||
|
animation: slideUp var(--transition-normal);
|
||||||
|
}
|
||||||
|
|
||||||
|
.animate-slideDown {
|
||||||
|
animation: slideDown var(--transition-normal);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Hide scrollbar but keep functionality */
|
||||||
|
.scrollbar-hide {
|
||||||
|
-ms-overflow-style: none;
|
||||||
|
scrollbar-width: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scrollbar-hide::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Truncate text */
|
||||||
|
.truncate {
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Line clamp */
|
||||||
|
.line-clamp-2 {
|
||||||
|
display: -webkit-box;
|
||||||
|
-webkit-line-clamp: 2;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.line-clamp-3 {
|
||||||
|
display: -webkit-box;
|
||||||
|
-webkit-line-clamp: 3;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
62
src/web/index.html
Normal file
62
src/web/index.html
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no, viewport-fit=cover" />
|
||||||
|
<meta name="theme-color" content="#1a1a2e" />
|
||||||
|
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||||
|
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
|
||||||
|
<meta name="description" content="Maestro Web Interface - Remote control for your AI coding assistants" />
|
||||||
|
<title>Maestro Web</title>
|
||||||
|
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||||
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600;700&display=swap" rel="stylesheet" />
|
||||||
|
<style>
|
||||||
|
/* Critical CSS for initial load */
|
||||||
|
* {
|
||||||
|
box-sizing: border-box;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
html, body, #root {
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
font-family: 'JetBrains Mono', 'Fira Code', 'Courier New', monospace;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
background-color: var(--color-background, #1a1a2e);
|
||||||
|
color: var(--color-text-main, #eaeaea);
|
||||||
|
}
|
||||||
|
/* Loading state */
|
||||||
|
.loading {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
height: 100%;
|
||||||
|
color: var(--color-text-main, #eaeaea);
|
||||||
|
}
|
||||||
|
.loading-spinner {
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
border: 3px solid var(--color-border, #333);
|
||||||
|
border-top-color: var(--color-accent, #4a9eff);
|
||||||
|
border-radius: 50%;
|
||||||
|
animation: spin 1s linear infinite;
|
||||||
|
}
|
||||||
|
@keyframes spin {
|
||||||
|
to { transform: rotate(360deg); }
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="root">
|
||||||
|
<div class="loading">
|
||||||
|
<div class="loading-spinner"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<script type="module" src="/main.tsx"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
140
src/web/main.tsx
Normal file
140
src/web/main.tsx
Normal file
@@ -0,0 +1,140 @@
|
|||||||
|
/**
|
||||||
|
* Maestro Web Interface Entry Point
|
||||||
|
*
|
||||||
|
* This is the main entry point for the web interface.
|
||||||
|
* It detects the device type and renders the appropriate interface.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React, { StrictMode, lazy, Suspense } from 'react';
|
||||||
|
import { createRoot } from 'react-dom/client';
|
||||||
|
import { ThemeProvider } from './components/ThemeProvider';
|
||||||
|
import './index.css';
|
||||||
|
|
||||||
|
// Lazy load mobile and desktop apps for code splitting
|
||||||
|
// These will be created in Phase 1 and Phase 2
|
||||||
|
const MobileApp = lazy(() =>
|
||||||
|
import('./mobile/App').catch(() => ({
|
||||||
|
default: () => <PlaceholderApp type="mobile" />,
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
|
||||||
|
const DesktopApp = lazy(() =>
|
||||||
|
import('./desktop/App').catch(() => ({
|
||||||
|
default: () => <PlaceholderApp type="desktop" />,
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Detect if the device is mobile based on screen size and touch capability
|
||||||
|
*/
|
||||||
|
function isMobileDevice(): boolean {
|
||||||
|
// Check for touch capability
|
||||||
|
const hasTouch = 'ontouchstart' in window || navigator.maxTouchPoints > 0;
|
||||||
|
|
||||||
|
// Check screen width (768px is a common breakpoint)
|
||||||
|
const isSmallScreen = window.innerWidth < 768;
|
||||||
|
|
||||||
|
// Check user agent for mobile indicators
|
||||||
|
const mobileUserAgent = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(
|
||||||
|
navigator.userAgent
|
||||||
|
);
|
||||||
|
|
||||||
|
// Consider mobile if small screen OR (has touch AND mobile user agent)
|
||||||
|
return isSmallScreen || (hasTouch && mobileUserAgent);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Placeholder component shown while the actual app loads
|
||||||
|
* or if the app module hasn't been created yet
|
||||||
|
*/
|
||||||
|
function PlaceholderApp({ type }: { type: 'mobile' | 'desktop' }) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
height: '100vh',
|
||||||
|
padding: '20px',
|
||||||
|
textAlign: 'center',
|
||||||
|
color: 'var(--color-text-main)',
|
||||||
|
backgroundColor: 'var(--color-background)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<h1 style={{ marginBottom: '16px', fontSize: '24px' }}>Maestro Web</h1>
|
||||||
|
<p style={{ marginBottom: '8px', color: 'var(--color-text-muted)' }}>
|
||||||
|
{type === 'mobile' ? 'Mobile' : 'Desktop'} interface coming soon
|
||||||
|
</p>
|
||||||
|
<p style={{ fontSize: '14px', color: 'var(--color-text-muted)' }}>
|
||||||
|
Connect to your Maestro desktop app to get started
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loading fallback component
|
||||||
|
*/
|
||||||
|
function LoadingFallback() {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
height: '100vh',
|
||||||
|
backgroundColor: 'var(--color-background)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
width: '40px',
|
||||||
|
height: '40px',
|
||||||
|
border: '3px solid var(--color-border)',
|
||||||
|
borderTopColor: 'var(--color-accent)',
|
||||||
|
borderRadius: '50%',
|
||||||
|
animation: 'spin 1s linear infinite',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Main App component that routes to mobile or desktop
|
||||||
|
*/
|
||||||
|
function App() {
|
||||||
|
const [isMobile, setIsMobile] = React.useState(isMobileDevice);
|
||||||
|
|
||||||
|
// Re-check on resize (for responsive design testing)
|
||||||
|
React.useEffect(() => {
|
||||||
|
const handleResize = () => {
|
||||||
|
setIsMobile(isMobileDevice());
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener('resize', handleResize);
|
||||||
|
return () => window.removeEventListener('resize', handleResize);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ThemeProvider>
|
||||||
|
<Suspense fallback={<LoadingFallback />}>
|
||||||
|
{isMobile ? <MobileApp /> : <DesktopApp />}
|
||||||
|
</Suspense>
|
||||||
|
</ThemeProvider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mount the application
|
||||||
|
const container = document.getElementById('root');
|
||||||
|
if (container) {
|
||||||
|
const root = createRoot(container);
|
||||||
|
root.render(
|
||||||
|
<StrictMode>
|
||||||
|
<App />
|
||||||
|
</StrictMode>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
console.error('Root element not found');
|
||||||
|
}
|
||||||
142
src/web/mobile/App.tsx
Normal file
142
src/web/mobile/App.tsx
Normal file
@@ -0,0 +1,142 @@
|
|||||||
|
/**
|
||||||
|
* Maestro Mobile Web App
|
||||||
|
*
|
||||||
|
* Lightweight remote control interface for mobile devices.
|
||||||
|
* Focused on quick command input and session monitoring.
|
||||||
|
*
|
||||||
|
* Phase 1 implementation will expand this component.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import { useThemeColors } from '../components/ThemeProvider';
|
||||||
|
|
||||||
|
export default function MobileApp() {
|
||||||
|
const colors = useThemeColors();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
height: '100vh',
|
||||||
|
backgroundColor: colors.background,
|
||||||
|
color: colors.textMain,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* Header */}
|
||||||
|
<header
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
padding: '12px 16px',
|
||||||
|
borderBottom: `1px solid ${colors.border}`,
|
||||||
|
backgroundColor: colors.surface,
|
||||||
|
}}
|
||||||
|
className="safe-area-top"
|
||||||
|
>
|
||||||
|
<h1 style={{ fontSize: '18px', fontWeight: 600 }}>Maestro</h1>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: '8px',
|
||||||
|
fontSize: '12px',
|
||||||
|
color: colors.textMuted,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
width: '8px',
|
||||||
|
height: '8px',
|
||||||
|
borderRadius: '50%',
|
||||||
|
backgroundColor: colors.warning,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
Connecting...
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
{/* Main content area */}
|
||||||
|
<main
|
||||||
|
style={{
|
||||||
|
flex: 1,
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
padding: '20px',
|
||||||
|
textAlign: 'center',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
marginBottom: '24px',
|
||||||
|
padding: '16px',
|
||||||
|
borderRadius: '12px',
|
||||||
|
backgroundColor: colors.surface,
|
||||||
|
border: `1px solid ${colors.border}`,
|
||||||
|
maxWidth: '300px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<h2 style={{ fontSize: '16px', marginBottom: '8px' }}>
|
||||||
|
Mobile Remote Control
|
||||||
|
</h2>
|
||||||
|
<p style={{ fontSize: '14px', color: colors.textMuted }}>
|
||||||
|
Send commands to your AI assistants from anywhere. This interface
|
||||||
|
will be implemented in Phase 1.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<p style={{ fontSize: '12px', color: colors.textMuted }}>
|
||||||
|
Make sure Maestro desktop app is running
|
||||||
|
</p>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
{/* Bottom input bar placeholder */}
|
||||||
|
<footer
|
||||||
|
style={{
|
||||||
|
padding: '12px 16px',
|
||||||
|
borderTop: `1px solid ${colors.border}`,
|
||||||
|
backgroundColor: colors.surface,
|
||||||
|
}}
|
||||||
|
className="safe-area-bottom"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
gap: '8px',
|
||||||
|
alignItems: 'center',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
placeholder="Enter command..."
|
||||||
|
disabled
|
||||||
|
style={{
|
||||||
|
flex: 1,
|
||||||
|
padding: '12px 16px',
|
||||||
|
borderRadius: '8px',
|
||||||
|
backgroundColor: colors.background,
|
||||||
|
border: `1px solid ${colors.border}`,
|
||||||
|
color: colors.textMuted,
|
||||||
|
fontSize: '14px',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
disabled
|
||||||
|
style={{
|
||||||
|
padding: '12px 16px',
|
||||||
|
borderRadius: '8px',
|
||||||
|
backgroundColor: colors.accent,
|
||||||
|
color: '#fff',
|
||||||
|
opacity: 0.5,
|
||||||
|
fontSize: '14px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Send
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
export default {
|
export default {
|
||||||
content: [
|
content: [
|
||||||
"./src/renderer/**/*.{js,ts,jsx,tsx}",
|
"./src/renderer/**/*.{js,ts,jsx,tsx}",
|
||||||
|
"./src/web/**/*.{js,ts,jsx,tsx}",
|
||||||
],
|
],
|
||||||
theme: {
|
theme: {
|
||||||
extend: {
|
extend: {
|
||||||
|
|||||||
111
vite.config.web.mts
Normal file
111
vite.config.web.mts
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
/**
|
||||||
|
* Vite configuration for Maestro Web Interface
|
||||||
|
*
|
||||||
|
* This config builds the web interface (both mobile and desktop)
|
||||||
|
* as a standalone bundle that can be served by the Fastify server.
|
||||||
|
*
|
||||||
|
* Output: dist/web/
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { defineConfig } from 'vite';
|
||||||
|
import react from '@vitejs/plugin-react';
|
||||||
|
import path from 'path';
|
||||||
|
import { readFileSync } from 'fs';
|
||||||
|
|
||||||
|
// Read version from package.json
|
||||||
|
const packageJson = JSON.parse(
|
||||||
|
readFileSync(path.join(__dirname, 'package.json'), 'utf-8')
|
||||||
|
);
|
||||||
|
const appVersion = process.env.VITE_APP_VERSION || packageJson.version;
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [react()],
|
||||||
|
|
||||||
|
// Entry point for web interface
|
||||||
|
root: path.join(__dirname, 'src/web'),
|
||||||
|
|
||||||
|
// Use relative paths for assets (served from Fastify)
|
||||||
|
base: './',
|
||||||
|
|
||||||
|
define: {
|
||||||
|
__APP_VERSION__: JSON.stringify(appVersion),
|
||||||
|
},
|
||||||
|
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
// Allow importing from renderer types/constants
|
||||||
|
'@renderer': path.join(__dirname, 'src/renderer'),
|
||||||
|
'@web': path.join(__dirname, 'src/web'),
|
||||||
|
'@shared': path.join(__dirname, 'src/shared'),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
build: {
|
||||||
|
outDir: path.join(__dirname, 'dist/web'),
|
||||||
|
emptyOutDir: true,
|
||||||
|
|
||||||
|
// Generate source maps for debugging
|
||||||
|
sourcemap: true,
|
||||||
|
|
||||||
|
rollupOptions: {
|
||||||
|
input: {
|
||||||
|
// Single entry point that handles routing to mobile/desktop
|
||||||
|
main: path.join(__dirname, 'src/web/index.html'),
|
||||||
|
},
|
||||||
|
output: {
|
||||||
|
// Organize output by type
|
||||||
|
entryFileNames: 'assets/[name]-[hash].js',
|
||||||
|
chunkFileNames: 'assets/[name]-[hash].js',
|
||||||
|
assetFileNames: 'assets/[name]-[hash].[ext]',
|
||||||
|
|
||||||
|
// Manual chunking for better caching
|
||||||
|
manualChunks: {
|
||||||
|
// React core in its own chunk
|
||||||
|
react: ['react', 'react-dom'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
// Target modern browsers (web interface doesn't need legacy support)
|
||||||
|
target: 'es2020',
|
||||||
|
|
||||||
|
// Minimize bundle size
|
||||||
|
minify: 'esbuild',
|
||||||
|
|
||||||
|
// Report chunk sizes
|
||||||
|
reportCompressedSize: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Development server (for testing web interface standalone)
|
||||||
|
server: {
|
||||||
|
port: 5174, // Different from renderer dev server (5173)
|
||||||
|
strictPort: true,
|
||||||
|
// Proxy API calls to the running Maestro app during development
|
||||||
|
proxy: {
|
||||||
|
'/api': {
|
||||||
|
target: 'http://localhost:45678',
|
||||||
|
changeOrigin: true,
|
||||||
|
},
|
||||||
|
'/ws': {
|
||||||
|
target: 'ws://localhost:45678',
|
||||||
|
ws: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
// Preview server for testing production build
|
||||||
|
preview: {
|
||||||
|
port: 5175,
|
||||||
|
strictPort: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Enable CSS code splitting
|
||||||
|
css: {
|
||||||
|
devSourcemap: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Optimize dependencies
|
||||||
|
optimizeDeps: {
|
||||||
|
include: ['react', 'react-dom'],
|
||||||
|
},
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user