Vehicoule commited on
Commit
83cb890
Β·
verified Β·
1 Parent(s): eb53fb1

# DeepSite - Complete Project Setup

Browse files

## Full Directory Structure

```
deepsite/
β”œβ”€β”€ backend/
β”‚ β”œβ”€β”€ package.json
β”‚ β”œβ”€β”€ .env
β”‚ β”œβ”€β”€ index.js
β”‚ β”œβ”€β”€ Dockerfile
β”‚ └── .dockerignore
β”œβ”€β”€ frontend/
β”‚ β”œβ”€β”€ package.json
β”‚ β”œβ”€β”€ .env
β”‚ β”œβ”€β”€ public/
β”‚ β”‚ β”œβ”€β”€ index.html
β”‚ β”‚ └── favicon.ico
β”‚ β”œβ”€β”€ src/
β”‚ β”‚ β”œβ”€β”€ index.js
β”‚ β”‚ β”œβ”€β”€ index.css
β”‚ β”‚ β”œβ”€β”€ App.jsx
β”‚ β”‚ β”œβ”€β”€ App.css
β”‚ β”‚ └── components/
β”‚ β”‚ β”œβ”€β”€ Header.jsx
β”‚ β”‚ β”œβ”€β”€ ChatPanel.jsx
β”‚ β”‚ β”œβ”€β”€ PreviewPanel.jsx
β”‚ β”‚ └── SettingsModal.jsx
β”‚ └── Dockerfile
β”œβ”€β”€ docker-compose.yml
β”œβ”€β”€ .gitignore
└── README.md
```

---

## Backend Files

### backend/package.json

```json
{
"name": "deepsite-backend",
"version": "1.0.0",
"description": "DeepSite API Backend",
"main": "index.js",
"scripts": {
"start": "node index.js",
"dev": "nodemon index.js"
},
"dependencies": {
"express": "^4.18.2",
"cors": "^2.8.5",
"dotenv": "^16.3.1",
"node-fetch": "^2.7.0"
},
"devDependencies": {
"nodemon": "^3.0.1"
}
}
```

### backend/.env

```
PORT=5000
NODE_ENV=development
```

### backend/index.js

```javascript
const express = require('express');
const cors = require('cors');
const fetch = require('node-fetch');
require('dotenv').config();

const app = express();
app.use(cors());
app.use(express.json({ limit: '50mb' }));
app.use(express.urlencoded({ limit: '50mb', extended: true }));

const SYSTEM_PROMPT = `You are an expert web developer. Create a complete, production-ready HTML/CSS/JavaScript website based on the user's request.

CRITICAL REQUIREMENTS:
- Return ONLY complete HTML code, nothing else
- Include ALL CSS in <style> tags in <head>
- Include ALL JavaScript in <script> tags before </body>
- Make it fully functional and interactive
- Use modern, responsive design (Tailwind CSS or inline styles)
- Include necessary CDN libraries
- DO NOT include markdown, explanations, or code blocks
- DO NOT wrap in triple backticks
- Start directly with <!DOCTYPE html>

Create a beautiful, modern, professional website!`;

// Health check
app.get('/api/health', (req, res) => {
res.json({ status: 'ok', timestamp: new Date().toISOString() });
});

// Validate API key
app.post('/api/validate-key', async (req, res) => {
try {
const { apiKey, baseUrl, provider } = req.body;

if (provider === 'llamacpp') {
return res.json({ valid: true });
}

const response = await fetch(`${baseUrl}/models`, {
headers: { 'Authorization': `Bearer ${apiKey}` }
});

res.json({ valid: response.ok });
} catch (err) {
res.json({ valid: false, error: err.message });
}
});

// Fetch models
app.post('/api/models', async (req, res) => {
try {
const { baseUrl, apiKey, provider } = req.body;

if (!baseUrl) {
return res.status(400).json({ error: 'Base URL required' });
}

let response;

if (provider === 'llamacpp') {
response = await fetch(`${baseUrl}/models`);
} else if (provider === 'nvidia') {
response = await fetch(`${baseUrl}/models`, {
headers: { 'Authorization': `Bearer ${apiKey}` }
});
} else if (provider === 'openrouter') {
response = await fetch('https://openrouter.ai/api/v1/models', {
headers: { 'Authorization': `Bearer ${apiKey}` }
});
}

if (!response.ok) {
return res.status(response.status).json({ error: 'Failed to fetch models' });
}

const data = await response.json();
const models = data.data || data.models || [];

res.json({ models });
} catch (err) {
res.status(500).json({ error: err.message });
}
});

// Generate website
app.post('/api/generate', async (req, res) => {
try {
const { model, apiKey, prompt, provider, baseUrl } = req.body;

if (!model || !prompt) {
return res.status(400).json({ error: 'Model and prompt required' });
}

if (provider !== 'llamacpp' && !apiKey) {
return res.status(400).json({ error: 'API key required' });
}

let response;

if (provider === 'llamacpp') {
response = await fetch(`${baseUrl}/v1/chat/completions`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
model,
messages: [
{ role: 'system', content: SYSTEM_PROMPT },
{ role: 'user', content: `Create a website for: ${prompt}` }
],
max_tokens: 16000,
temperature: 0.7,
stream: false
})
});
} else if (provider === 'nvidia') {
response = await fetch(`${baseUrl}/chat/completions`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${apiKey}`
},
body: JSON.stringify({
model,
messages: [
{ role: 'system', content: SYSTEM_PROMPT },
{ role: 'user', content: `Create a website for: ${prompt}` }
],
max_tokens: 16000,
temperature: 0.7,
stream: false
})
});
} else if (provider === 'openrouter') {
response = await fetch(`${baseUrl}/chat/completions`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${apiKey}`,
'HTTP-Referer': 'http://localhost:3000'
},
body: JSON.stringify({
model,
messages: [
{ role: 'system', content: SYSTEM_PROMPT },
{ role: 'user', content: `Create a website for: ${prompt}` }
],
max_tokens: 16000,
temperature: 0.7,
stream: false
})
});
}

const data = await response.json();

if (!response.ok) {
return res.status(response.status).json({
error: data.error?.message || data.detail || 'Generation failed'
});
}

let code = data.choices[0].message.content;
code = code.replace(/```html\n?/gi, '').replace(/```\n?/g, '').trim();

const htmlMatch = code.match(/<!DOCTYPE html>[\s\S]*<\/html>/i);
if (htmlMatch) {
code = htmlMatch[0];
}

res.json({ code });
} catch (err) {
res.status(500).json({ error: err.message });
}
});

const PORT = process.env.PORT || 5000;
app.listen(PORT, () => {
console.log(`βœ“ DeepSite Backend running on port ${PORT}`);
console.log(`βœ“ Health check: http://localhost:${PORT}/api/health`);
});
```

### backend/Dockerfile

```dockerfile
FROM node:18-alpine

WORKDIR /app

COPY package*.json ./

RUN npm install --production

COPY . .

EXPOSE 5000

CMD ["npm", "start"]
```

### backend/.dockerignore

```
node_modules
npm-debug.log
.env.local
```

---

## Frontend Files

### frontend/package.json

```json
{
"name": "deepsite-frontend",
"version": "1.0.0",
"private": true,
"proxy": "http://localhost:5000",
"dependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-scripts": "5.0.1",
"lucide-react": "^0.263.1",
"axios": "^1.5.0"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": [
"react-app"
]
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
}
}
```

### frontend/.env

```
REACT_APP_API_URL=http://localhost:5000
```

### frontend/public/index.html

```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta name="description" content="DeepSite - AI Web Builder" />
<title>DeepSite - AI Web Builder</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
</body>
</html>
```

### frontend/src/index.js

```javascript
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
```

### frontend/src/index.css

```css
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}

html, body, #root {
height: 100%;
width: 100%;
}

body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
background: linear-gradient(to bottom right, #f8fafc, #ffffff, #f0f4f8);
}

button {
cursor: pointer;
border: none;
font-family: inherit;
}

input, textarea, select {
font-family: inherit;
}

/* Scrollbar styling */
::-webkit-scrollbar {
width: 8px;
height: 8px;
}

::-webkit-scrollbar-track {
background: #f1f1f1;
border-radius: 10px;
}

::-webkit-scrollbar-thumb {
background: #888;
border-radius: 10px;
}

::-webkit-scrollbar-thumb:hover {
background: #555;
}
```

### frontend/src/App.jsx

```jsx
import React, { useState, useEffect } from 'react';
import { Settings } from 'lucide-react';
import './App.css';
import Header from './components/Header';
import ChatPanel from './components/ChatPanel';
import PreviewPanel from './components/PreviewPanel';
import SettingsModal from './components/SettingsModal';

const API_URL = process.env.REACT_APP_API_URL || 'http://localhost:5000';

export default function App() {
const [provider, setProvider] = useState('nvidia');
const [apiKey, setApiKey] = useState('');
const [baseUrl, setBaseUrl] = useState('https://integrate.api.nvidia.com/v1');
const [model, setModel] = useState('');
const [models, setModels] = useState([]);
const [prompt, setPrompt] = useState('');

README.md CHANGED
@@ -1,10 +1,13 @@
1
  ---
2
- title: Deepsite Ai Builder
3
- emoji: πŸ“Š
4
- colorFrom: green
5
- colorTo: green
6
  sdk: static
7
  pinned: false
 
 
8
  ---
9
 
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
1
  ---
2
+ title: DeepSite AI Builder 🎨
3
+ colorFrom: blue
4
+ colorTo: gray
5
+ emoji: 🐳
6
  sdk: static
7
  pinned: false
8
+ tags:
9
+ - deepsite-v3
10
  ---
11
 
12
+ # Welcome to your new DeepSite project!
13
+ This project was created with [DeepSite](https://huggingface.co/deepsite).
backend/.dockerignore ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ ```
2
+ node_modules
3
+ npm-debug.log
4
+ .env.local
5
+ .env.production
6
+ .DS_Store
7
+ .git
8
+ .gitignore
9
+ README.md
10
+ ```
backend/.env ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ ```
2
+ PORT=5000
3
+ NODE_ENV=development
4
+ OPENAI_API_KEY=your_openai_key_here
5
+ NVIDIA_API_KEY=your_nvidia_key_here
6
+ ```
backend/Dockerfile ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ```dockerfile
2
+ FROM node:18-alpine
3
+
4
+ WORKDIR /app
5
+
6
+ # Copy package files
7
+ COPY package*.json ./
8
+
9
+ # Install dependencies
10
+ RUN npm install --only=production
11
+
12
+ # Copy source code
13
+ COPY . .
14
+
15
+ # Create non-root user
16
+ RUN addgroup -g 1001 -S nodejs
17
+ RUN adduser -S deepsite -u 1001
18
+
19
+ # Change ownership of the app directory
20
+ RUN chown -R deepsite:nodejs /app
21
+ USER deepsite
22
+
23
+ EXPOSE 5000
24
+
25
+ # Health check
26
+ HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
27
+ CMD node -e "require('http').get('http://localhost:5000/api/health', (res) => { process.exit(res.statusCode === 200 ? 0 : 1) })"
28
+
29
+ CMD ["npm", "start"]
30
+ ```
backend/index.js ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ \n?/gi, '')
2
+ .replace(/
backend/package.json ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ```json
2
+ {
3
+ "name": "deepsite-backend",
4
+ "version": "1.0.0",
5
+ "description": "DeepSite AI Web Builder Backend API",
6
+ "main": "index.js",
7
+ "scripts": {
8
+ "start": "node index.js",
9
+ "dev": "nodemon index.js"
10
+ },
11
+ "dependencies": {
12
+ "express": "^4.18.2",
13
+ "cors": "^2.8.5",
14
+ "dotenv": "^16.3.1",
15
+ "node-fetch": "^2.7.0",
16
+ "axios": "^1.5.0"
17
+ },
18
+ "devDependencies": {
19
+ "nodemon": "^3.0.1"
20
+ }
21
+ }
22
+ ```
frontend/.env ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ ```
2
+ REACT_APP_API_URL=http://localhost:5000
3
+ REACT_APP_VERSION=1.0.0
4
+ ```
frontend/package.json ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ```json
2
+ {
3
+ "name": "deepsite-frontend",
4
+ "version": "1.0.0",
5
+ "private": true,
6
+ "proxy": "http://localhost:5000",
7
+ "dependencies": {
8
+ "react": "^18.2.0",
9
+ "react-dom": "^18.2.0",
10
+ "react-scripts": "5.0.1",
11
+ "lucide-react": "^0.263.1",
12
+ "axios": "^1.5.0",
13
+ "tailwindcss": "^3.3.0",
14
+ "autoprefixer": "^10.4.15",
15
+ "postcss": "^8.4.27",
16
+ "@types/react": "^18.2.21",
17
+ "@types/react-dom": "^18.2.7",
18
+ "typescript": "^4.9.5"
19
+ },
20
+ "scripts": {
21
+ "start": "react-scripts start",
22
+ "build": "react-scripts build",
23
+ "test": "react-scripts test",
24
+ "eject": "react-scripts eject"
25
+ },
26
+ "eslintConfig": {
27
+ "extends": [
28
+ "react-app",
29
+ "react-app/jest"
30
+ ]
31
+ },
32
+ "browserslist": {
33
+ "production": [
34
+ ">0.2%",
35
+ "not dead",
36
+ "not op_mini all"
37
+ ],
38
+ "development": [
39
+ "last 1 chrome version",
40
+ "last 1 firefox version",
41
+ "last 1 safari version"
42
+ ]
43
+ },
44
+ "devDependencies": {
45
+ "@types/node": "^20.5.0"
46
+ }
47
+ }
48
+ ```
frontend/public/index.html ADDED
@@ -0,0 +1,89 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <meta name="theme-color" content="#000000" />
7
+ <meta name="description" content="DeepSite - AI-Powered Website Builder" />
8
+ <meta name="keywords" content="AI, website builder, React, full-stack, code generation" />
9
+ <title>DeepSite - AI Website Builder</title>
10
+
11
+ <!-- Preload fonts -->
12
+ <link rel="preconnect" href="https://fonts.googleapis.com">
13
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
14
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
15
+
16
+ <!-- Favicon -->
17
+ <link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
18
+
19
+ <!-- Progressive Web App -->
20
+ <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
21
+
22
+ <!-- Open Graph -->
23
+ <meta property="og:title" content="DeepSite - AI Website Builder" />
24
+ <meta property="og:description" content="Create beautiful websites with AI" />
25
+ <meta property="og:type" content="website" />
26
+ <meta property="og:image" content="%PUBLIC_URL%/og-image.png" />
27
+ </head>
28
+ <body>
29
+ <noscript>You need to enable JavaScript to run this app.</noscript>
30
+ <div id="root"></div>
31
+
32
+ <!-- Loading screen -->
33
+ <style>
34
+ #initial-loader {
35
+ position: fixed;
36
+ top: 0;
37
+ left: 0;
38
+ width: 100%;
39
+ height: 100%;
40
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
41
+ display: flex;
42
+ align-items: center;
43
+ justify-content: center;
44
+ z-index: 9999;
45
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif;
46
+ }
47
+
48
+ #initial-loader .loader-content {
49
+ text-align: center;
50
+ color: white;
51
+ }
52
+
53
+ #initial-loader .spinner {
54
+ width: 40px;
55
+ height: 40px;
56
+ border: 3px solid rgba(255,255,255,0.3);
57
+ border-radius: 50%;
58
+ border-top-color: white;
59
+ animation: spin 1s ease-in-out infinite;
60
+ margin: 0 auto 16px;
61
+ }
62
+
63
+ @keyframes spin {
64
+ to { transform: rotate(360deg); }
65
+ }
66
+ </style>
67
+
68
+ <div id="initial-loader">
69
+ <div class="loader-content">
70
+ <div class="spinner"></div>
71
+ <h2>Loading DeepSite...</h2>
72
+ <p>Preparing your AI website builder</p>
73
+ </div>
74
+ </div>
75
+
76
+ <script>
77
+ // Remove loader when React app loads
78
+ window.addEventListener('load', function() {
79
+ setTimeout(function() {
80
+ const loader = document.getElementById('initial-loader');
81
+ if (loader) {
82
+ loader.style.opacity = '0';
83
+ setTimeout(() => loader.remove(), 300);
84
+ }
85
+ }, 1000);
86
+ });
87
+ </script>
88
+ </body>
89
+ </html>
frontend/src/App.css ADDED
@@ -0,0 +1,317 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* App Container */
2
+ .app-container {
3
+ display: flex;
4
+ flex-direction: column;
5
+ height: 100vh;
6
+ width: 100%;
7
+ overflow: hidden;
8
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
9
+ }
10
+
11
+ /* Status Bar */
12
+ .status-bar {
13
+ display: flex;
14
+ align-items: center;
15
+ justify-content: space-between;
16
+ padding: 0.5rem 1rem;
17
+ background: rgba(255, 255, 255, 0.1);
18
+ backdrop-filter: blur(10px);
19
+ border-bottom: 1px solid rgba(255, 255, 255, 0.2);
20
+ z-index: 100;
21
+ }
22
+
23
+ .status-bar.online {
24
+ border-left: 4px solid var(--success-500);
25
+ }
26
+
27
+ .status-bar.offline {
28
+ border-left: 4px solid var(--error-500);
29
+ }
30
+
31
+ .status-bar.checking {
32
+ border-left: 4px solid var(--warning-500);
33
+ }
34
+
35
+ .status-dot {
36
+ width: 8px;
37
+ height: 8px;
38
+ border-radius: 50%;
39
+ animation: pulse 2s infinite;
40
+ }
41
+
42
+ .status-dot.online {
43
+ background-color: var(--success-500);
44
+ }
45
+
46
+ .status-dot.offline {
47
+ background-color: var(--error-500);
48
+ }
49
+
50
+ .status-dot.checking {
51
+ background-color: var(--warning-500);
52
+ }
53
+
54
+ @keyframes pulse {
55
+ 0% {
56
+ transform: scale(0.95);
57
+ box-shadow: 0 0 0 0 rgba(59, 130, 246, 0.7);
58
+ }
59
+
60
+ 70% {
61
+ transform: scale(1);
62
+ box-shadow: 0 0 0 10px rgba(59, 130, 246, 0);
63
+ }
64
+
65
+ 100% {
66
+ transform: scale(0.95);
67
+ box-shadow: 0 0 0 0 rgba(59, 130, 246, 0);
68
+ }
69
+ }
70
+
71
+ /* Main Content */
72
+ .main-content {
73
+ display: flex;
74
+ gap: 1rem;
75
+ flex: 1;
76
+ padding: 1rem;
77
+ overflow: hidden;
78
+ min-height: 0;
79
+ }
80
+
81
+ @media (max-width: 768px) {
82
+ .main-content {
83
+ flex-direction: column;
84
+ gap: 0.5rem;
85
+ padding: 0.5rem;
86
+ }
87
+ }
88
+
89
+ /* Settings Button */
90
+ .settings-button {
91
+ position: fixed;
92
+ bottom: 1.5rem;
93
+ right: 1.5rem;
94
+ display: flex;
95
+ align-items: center;
96
+ gap: 0.5rem;
97
+ padding: 0.75rem 1rem;
98
+ border-radius: 9999px;
99
+ background: rgba(255, 255, 255, 0.9);
100
+ backdrop-filter: blur(10px);
101
+ border: 1px solid rgba(0, 0, 0, 0.1);
102
+ color: var(--gray-900);
103
+ font-weight: 500;
104
+ font-size: 0.875rem;
105
+ transition: all 0.2s ease;
106
+ z-index: 40;
107
+ box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
108
+ }
109
+
110
+ .settings-button:hover {
111
+ background: rgba(255, 255, 255, 1);
112
+ transform: translateY(-1px);
113
+ box-shadow: 0 8px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
114
+ }
115
+
116
+ @media (max-width: 768px) {
117
+ .settings-button {
118
+ bottom: 1rem;
119
+ right: 1rem;
120
+ padding: 0.75rem;
121
+ border-radius: 50%;
122
+ }
123
+
124
+ .settings-button span {
125
+ display: none;
126
+ }
127
+ }
128
+
129
+ /* Modal Styles */
130
+ .modal-overlay {
131
+ position: fixed;
132
+ top: 0;
133
+ left: 0;
134
+ right: 0;
135
+ bottom: 0;
136
+ background: rgba(0, 0, 0, 0.5);
137
+ backdrop-filter: blur(4px);
138
+ display: flex;
139
+ align-items: center;
140
+ justify-content: center;
141
+ z-index: 50;
142
+ padding: 1rem;
143
+ }
144
+
145
+ .modal {
146
+ background: white;
147
+ border-radius: 1rem;
148
+ box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
149
+ max-width: 500px;
150
+ width: 100%;
151
+ max-height: 90vh;
152
+ overflow: hidden;
153
+ display: flex;
154
+ flex-direction: column;
155
+ }
156
+
157
+ .modal-header {
158
+ display: flex;
159
+ align-items: center;
160
+ justify-content: space-between;
161
+ padding: 1.5rem;
162
+ border-bottom: 1px solid var(--gray-200);
163
+ }
164
+
165
+ .modal-header h2 {
166
+ font-size: 1.25rem;
167
+ font-weight: 600;
168
+ color: var(--gray-900);
169
+ }
170
+
171
+ .modal-header button {
172
+ width: 32px;
173
+ height: 32px;
174
+ display: flex;
175
+ align-items: center;
176
+ justify-content: center;
177
+ border-radius: 50%;
178
+ color: var(--gray-500);
179
+ transition: all 0.2s;
180
+ }
181
+
182
+ .modal-header button:hover {
183
+ background: var(--gray-100);
184
+ color: var(--gray-700);
185
+ }
186
+
187
+ .modal-content {
188
+ flex: 1;
189
+ overflow-y: auto;
190
+ padding: 1.5rem;
191
+ }
192
+
193
+ .modal-close-btn {
194
+ padding: 1rem 1.5rem;
195
+ background: var(--gray-50);
196
+ border: none;
197
+ font-weight: 500;
198
+ color: var(--gray-700);
199
+ transition: all 0.2s;
200
+ border-top: 1px solid var(--gray-200);
201
+ }
202
+
203
+ .modal-close-btn:hover {
204
+ background: var(--gray-100);
205
+ }
206
+
207
+ /* About Content */
208
+ .about-content {
209
+ text-align: left;
210
+ }
211
+
212
+ .feature-list ul {
213
+ list-style: none;
214
+ padding: 0;
215
+ }
216
+
217
+ .tech-badge {
218
+ display: inline-block;
219
+ padding: 0.25rem 0.5rem;
220
+ background: var(--gray-100);
221
+ color: var(--gray-700);
222
+ font-size: 0.75rem;
223
+ font-weight: 500;
224
+ border-radius: 9999px;
225
+ border: 1px solid var(--gray-200);
226
+ }
227
+
228
+ /* Responsive adjustments */
229
+ @media (max-width: 640px) {
230
+ .modal {
231
+ margin: 0.5rem;
232
+ max-height: calc(100vh - 1rem);
233
+ }
234
+
235
+ .modal-header {
236
+ padding: 1rem;
237
+ }
238
+
239
+ .modal-content {
240
+ padding: 1rem;
241
+ }
242
+
243
+ .modal-close-btn {
244
+ padding: 0.75rem 1rem;
245
+ }
246
+ }
247
+
248
+ /* Loading states */
249
+ .loading-overlay {
250
+ position: absolute;
251
+ top: 0;
252
+ left: 0;
253
+ right: 0;
254
+ bottom: 0;
255
+ background: rgba(255, 255, 255, 0.8);
256
+ display: flex;
257
+ align-items: center;
258
+ justify-content: center;
259
+ z-index: 10;
260
+ }
261
+
262
+ .loading-spinner {
263
+ width: 40px;
264
+ height: 40px;
265
+ border: 3px solid var(--gray-200);
266
+ border-top: 3px solid var(--primary-500);
267
+ border-radius: 50%;
268
+ animation: spin 1s linear infinite;
269
+ }
270
+
271
+ /* Error and Success Messages */
272
+ .message-bar {
273
+ position: fixed;
274
+ top: 1rem;
275
+ left: 50%;
276
+ transform: translateX(-50%);
277
+ padding: 0.75rem 1rem;
278
+ border-radius: 0.5rem;
279
+ font-weight: 500;
280
+ font-size: 0.875rem;
281
+ z-index: 60;
282
+ animation: slideDown 0.3s ease-out;
283
+ display: flex;
284
+ align-items: center;
285
+ gap: 0.5rem;
286
+ box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
287
+ }
288
+
289
+ .message-bar.error {
290
+ background: var(--error-500);
291
+ color: white;
292
+ }
293
+
294
+ .message-bar.success {
295
+ background: var(--success-500);
296
+ color: white;
297
+ }
298
+
299
+ @keyframes slideDown {
300
+ from {
301
+ opacity: 0;
302
+ transform: translateX(-50%) translateY(-100%);
303
+ }
304
+ to {
305
+ opacity: 1;
306
+ transform: translateX(-50%) translateY(0);
307
+ }
308
+ }
309
+
310
+ /* Print styles */
311
+ @media print {
312
+ .settings-button,
313
+ .status-bar,
314
+ .modal-overlay {
315
+ display: none !important;
316
+ }
317
+ }
frontend/src/App.tsx ADDED
@@ -0,0 +1,278 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ```typescript
2
+ import React, { useState, useEffect, useCallback } from 'react';
3
+ import { Settings, Github, Heart } from 'lucide-react';
4
+ import Header from './components/Header';
5
+ import ChatPanel from './components/ChatPanel';
6
+ import PreviewPanel from './components/PreviewPanel';
7
+ import SettingsModal from './components/SettingsModal';
8
+ import { ApiService } from './services/api';
9
+ import { ChatMessage, ApiProvider, AppSettings } from './types';
10
+
11
+ const API_URL = process.env.REACT_APP_API_URL || 'http://localhost:5000';
12
+
13
+ const defaultSettings: AppSettings = {
14
+ provider: 'openai',
15
+ apiKey: '',
16
+ model: 'gpt-4',
17
+ theme: 'light'
18
+ };
19
+
20
+ function App() {
21
+ const [settings, setSettings] = useState<AppSettings>(defaultSettings);
22
+ const [prompt, setPrompt] = useState('');
23
+ const [generatedCode, setGeneratedCode] = useState('');
24
+ const [messages, setMessages] = useState<ChatMessage[]>([]);
25
+ const [loading, setLoading] = useState(false);
26
+ const [error, setError] = useState('');
27
+ const [success, setSuccess] = useState('');
28
+ const [showSettings, setShowSettings] = useState(false);
29
+ const [showAbout, setShowAbout] = useState(false);
30
+ const [apiStatus, setApiStatus] = useState<'checking' | 'online' | 'offline'>('checking');
31
+
32
+ // Load settings from localStorage
33
+ useEffect(() => {
34
+ const savedSettings = localStorage.getItem('deepsite-settings');
35
+ if (savedSettings) {
36
+ try {
37
+ const parsed = JSON.parse(savedSettings);
38
+ setSettings({ ...defaultSettings, ...parsed });
39
+ } catch (e) {
40
+ console.error('Error loading settings:', e);
41
+ }
42
+ }
43
+ checkApiHealth();
44
+ }, []);
45
+
46
+ // Save settings to localStorage
47
+ useEffect(() => {
48
+ localStorage.setItem('deepsite-settings', JSON.stringify(settings));
49
+ }, [settings]);
50
+
51
+ // Check API health
52
+ const checkApiHealth = useCallback(async () => {
53
+ try {
54
+ await ApiService.healthCheck();
55
+ setApiStatus('online');
56
+ } catch (error) {
57
+ setApiStatus('offline');
58
+ }
59
+ }, []);
60
+
61
+ // Generate website
62
+ const generateWebsite = async () => {
63
+ if (!prompt.trim()) {
64
+ setError('Please describe what you want to build');
65
+ setTimeout(() => setError(''), 3000);
66
+ return;
67
+ }
68
+
69
+ if (settings.provider !== 'anthropic' && !settings.apiKey.trim()) {
70
+ setError('Please configure your API key in settings');
71
+ setTimeout(() => setError(''), 3000);
72
+ return;
73
+ }
74
+
75
+ setLoading(true);
76
+ setError('');
77
+ setSuccess('');
78
+
79
+ // Add user message
80
+ const userMessage: ChatMessage = {
81
+ id: Date.now().toString(),
82
+ role: 'user',
83
+ content: prompt,
84
+ timestamp: new Date()
85
+ };
86
+ setMessages(prev => [...prev, userMessage]);
87
+
88
+ try {
89
+ const response = await ApiService.generate({
90
+ prompt,
91
+ provider: settings.provider,
92
+ model: settings.model,
93
+ apiKey: settings.apiKey
94
+ });
95
+
96
+ const aiMessage: ChatMessage = {
97
+ id: (Date.now() + 1).toString(),
98
+ role: 'assistant',
99
+ content: '✨ Your website has been generated successfully!',
100
+ timestamp: new Date()
101
+ };
102
+
103
+ setMessages(prev => [...prev, aiMessage]);
104
+ setGeneratedCode(response.code);
105
+ setSuccess('Website generated successfully!');
106
+ setPrompt('');
107
+
108
+ setTimeout(() => setSuccess(''), 3000);
109
+ } catch (err) {
110
+ const errorMessage = err instanceof Error ? err.message : 'Generation failed';
111
+ setError(errorMessage);
112
+
113
+ const errorAiMessage: ChatMessage = {
114
+ id: (Date.now() + 1).toString(),
115
+ role: 'assistant',
116
+ content: `❌ Error: ${errorMessage}`,
117
+ timestamp: new Date()
118
+ };
119
+
120
+ setMessages(prev => [...prev, errorAiMessage]);
121
+ } finally {
122
+ setLoading(false);
123
+ }
124
+ };
125
+
126
+ // Handle keyboard shortcuts
127
+ useEffect(() => {
128
+ const handleKeyDown = (e: KeyboardEvent) => {
129
+ if (e.key === 'Enter' && (e.metaKey || e.ctrlKey)) {
130
+ e.preventDefault();
131
+ if (!loading && prompt.trim()) {
132
+ generateWebsite();
133
+ }
134
+ }
135
+ if (e.key === 'Escape') {
136
+ setShowSettings(false);
137
+ setShowAbout(false);
138
+ }
139
+ };
140
+
141
+ document.addEventListener('keydown', handleKeyDown);
142
+ return () => document.removeEventListener('keydown', handleKeyDown);
143
+ }, [prompt, loading, generateWebsite]);
144
+
145
+ return (
146
+ <div className="app-container">
147
+ {/* Status Bar */}
148
+ <div className={`status-bar ${apiStatus === 'online' ? 'online' : apiStatus === 'offline' ? 'offline' : 'checking'}`}>
149
+ <div className="flex items-center gap-2">
150
+ <div className={`status-dot ${apiStatus}`}></div>
151
+ <span className="text-sm">
152
+ {apiStatus === 'online' && 'API Connected'}
153
+ {apiStatus === 'offline' && 'API Disconnected'}
154
+ {apiStatus === 'checking' && 'Checking API...'}
155
+ </span>
156
+ </div>
157
+ <div className="flex items-center gap-4">
158
+ <span className="text-sm text-gray-600">DeepSite v{process.env.REACT_APP_VERSION}</span>
159
+ <button
160
+ onClick={() => setShowAbout(true)}
161
+ className="text-sm text-gray-600 hover:text-gray-800 transition-colors"
162
+ >
163
+ About
164
+ </button>
165
+ </div>
166
+ </div>
167
+
168
+ {/* Main Header */}
169
+ <Header
170
+ prompt={prompt}
171
+ setPrompt={setPrompt}
172
+ onGenerate={generateWebsite}
173
+ loading={loading}
174
+ onSettingsClick={() => setShowSettings(true)}
175
+ />
176
+
177
+ {/* Main Content */}
178
+ <div className="main-content">
179
+ <ChatPanel messages={messages} />
180
+ <PreviewPanel
181
+ generatedCode={generatedCode}
182
+ error={error}
183
+ success={success}
184
+ loading={loading}
185
+ />
186
+ </div>
187
+
188
+ {/* Settings Button */}
189
+ <button
190
+ className="settings-button"
191
+ onClick={() => setShowSettings(true)}
192
+ title="Settings (⌘+,)"
193
+ >
194
+ <Settings size={18} />
195
+ <span className="mobile-hidden">Settings</span>
196
+ </button>
197
+
198
+ {/* About Modal */}
199
+ {showAbout && (
200
+ <div className="modal-overlay" onClick={() => setShowAbout(false)}>
201
+ <div className="modal scale-in" onClick={(e) => e.stopPropagation()}>
202
+ <div className="modal-header">
203
+ <h2>About DeepSite</h2>
204
+ <button onClick={() => setShowAbout(false)}>Γ—</button>
205
+ </div>
206
+ <div className="modal-content">
207
+ <div className="about-content">
208
+ <div className="text-center mb-6">
209
+ <h3 className="text-2xl font-bold bg-gradient-to-r from-blue-600 to-purple-600 bg-clip-text text-transparent">
210
+ DeepSite
211
+ </h3>
212
+ <p className="text-gray-600 mt-2">AI-Powered Website Builder</p>
213
+ </div>
214
+
215
+ <div className="space-y-4">
216
+ <div className="feature-list">
217
+ <h4 className="font-semibold mb-2">Features:</h4>
218
+ <ul className="space-y-1 text-sm text-gray-600">
219
+ <li>β€’ Generate complete websites with AI</li>
220
+ <li>β€’ Support for multiple AI providers</li>
221
+ <li>β€’ Real-time preview and code editing</li>
222
+ <li>β€’ Responsive design generation</li>
223
+ <li>β€’ Modern React-based interface</li>
224
+ </ul>
225
+ </div>
226
+
227
+ <div className="tech-stack">
228
+ <h4 className="font-semibold mb-2">Built with:</h4>
229
+ <div className="flex flex-wrap gap-2">
230
+ <span className="tech-badge">React</span>
231
+ <span className="tech-badge">TypeScript</span>
232
+ <span className="tech-badge">Tailwind CSS</span>
233
+ <span className="tech-badge">Node.js</span>
234
+ <span className="tech-badge">Express</span>
235
+ </div>
236
+ </div>
237
+ </div>
238
+
239
+ <div className="text-center mt-6 pt-4 border-t border-gray-200">
240
+ <p className="text-sm text-gray-500 mb-3">
241
+ Made with <Heart size={16} className="inline text-red-500" /> for the community
242
+ </p>
243
+ <div className="flex justify-center gap-4">
244
+ <a
245
+ href="https://github.com/deepsite/deepsite"
246
+ target="_blank"
247
+ rel="noopener noreferrer"
248
+ className="inline-flex items-center gap-2 text-sm text-gray-600 hover:text-gray-800 transition-colors"
249
+ >
250
+ <Github size={16} />
251
+ View on GitHub
252
+ </a>
253
+ </div>
254
+ </div>
255
+ </div>
256
+ </div>
257
+ <button className="modal-close-btn" onClick={() => setShowAbout(false)}>
258
+ Close
259
+ </button>
260
+ </div>
261
+ </div>
262
+ )}
263
+
264
+ {/* Settings Modal */}
265
+ {showSettings && (
266
+ <SettingsModal
267
+ settings={settings}
268
+ onSettingsChange={setSettings}
269
+ onClose={() => setShowSettings(false)}
270
+ onApiStatusCheck={checkApiHealth}
271
+ />
272
+ )}
273
+ </div>
274
+ );
275
+ }
276
+
277
+ export default App;
278
+ ```
frontend/src/components/ChatPanel.tsx ADDED
@@ -0,0 +1,130 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /** @type {import('tailwindcss').Config} */
2
+ module.exports = {
3
+ content: [
4
+ "./src/**/*.{js,jsx,ts,tsx}",
5
+ "./public/index.html"
6
+ ],
7
+ theme: {
8
+ extend: {
9
+ fontFamily: {
10
+ sans: ['Inter', 'ui-sans-serif', 'system-ui', '-apple-system', 'BlinkMacSystemFont', 'Segoe UI', 'Roboto', 'Helvetica Neue', 'Arial', 'Noto Sans', 'sans-serif'],
11
+ },
12
+ colors: {
13
+ primary: {
14
+ 50: '#eff6ff',
15
+ 100: '#dbeafe',
16
+ 200: '#bfdbfe',
17
+ 300: '#93c5fd',
18
+ 400: '#60a5fa',
19
+ 500: '#3b82f6',
20
+ 600: '#2563eb',
21
+ 700: '#1d4ed8',
22
+ 800: '#1e40af',
23
+ 900: '#1e3a8a',
24
+ },
25
+ secondary: {
26
+ 50: '#faf5ff',
27
+ 100: '#f3e8ff',
28
+ 200: '#e9d5ff',
29
+ 300: '#d8b4fe',
30
+ 400: '#c084fc',
31
+ 500: '#a855f7',
32
+ 600: '#9333ea',
33
+ 700: '#7c3aed',
34
+ 800: '#6b21a8',
35
+ 900: '#581c87',
36
+ },
37
+ success: {
38
+ 50: '#f0fdf4',
39
+ 100: '#dcfce7',
40
+ 200: '#bbf7d0',
41
+ 300: '#86efac',
42
+ 400: '#4ade80',
43
+ 500: '#22c55e',
44
+ 600: '#16a34a',
45
+ 700: '#15803d',
46
+ 800: '#166534',
47
+ 900: '#14532d',
48
+ },
49
+ warning: {
50
+ 50: '#fffbeb',
51
+ 100: '#fef3c7',
52
+ 200: '#fde68a',
53
+ 300: '#fcd34d',
54
+ 400: '#fbbf24',
55
+ 500: '#f59e0b',
56
+ 600: '#d97706',
57
+ 700: '#b45309',
58
+ 800: '#92400e',
59
+ 900: '#78350f',
60
+ },
61
+ error: {
62
+ 50: '#fef2f2',
63
+ 100: '#fee2e2',
64
+ 200: '#fecaca',
65
+ 300: '#fca5a5',
66
+ 400: '#f87171',
67
+ 500: '#ef4444',
68
+ 600: '#dc2626',
69
+ 700: '#b91c1c',
70
+ 800: '#991b1b',
71
+ 900: '#7f1d1d',
72
+ }
73
+ },
74
+ animation: {
75
+ 'fade-in': 'fadeIn 0.5s ease-in-out',
76
+ 'slide-up': 'slideUp 0.3s ease-out',
77
+ 'slide-down': 'slideDown 0.3s ease-out',
78
+ 'scale-in': 'scaleIn 0.2s ease-out',
79
+ 'bounce-gentle': 'bounceGentle 2s infinite',
80
+ 'pulse-slow': 'pulse 3s infinite',
81
+ },
82
+ keyframes: {
83
+ fadeIn: {
84
+ '0%': { opacity: '0' },
85
+ '100%': { opacity: '1' },
86
+ },
87
+ slideUp: {
88
+ '0%': { opacity: '0', transform: 'translateY(20px)' },
89
+ '100%': { opacity: '1', transform: 'translateY(0)' },
90
+ },
91
+ slideDown: {
92
+ '0%': { opacity: '0', transform: 'translateY(-20px)' },
93
+ '100%': { opacity: '1', transform: 'translateY(0)' },
94
+ },
95
+ scaleIn: {
96
+ '0%': { opacity: '0', transform: 'scale(0.95)' },
97
+ '100%': { opacity: '1', transform: 'scale(1)' },
98
+ },
99
+ bounceGentle: {
100
+ '0%, 100%': { transform: 'translateY(0)' },
101
+ '50%': { transform: 'translateY(-5px)' },
102
+ }
103
+ },
104
+ backdropBlur: {
105
+ xs: '2px',
106
+ },
107
+ boxShadow: {
108
+ 'glow': '0 0 20px rgba(59, 130, 246, 0.3)',
109
+ 'glow-lg': '0 0 40px rgba(59, 130, 246, 0.4)',
110
+ },
111
+ spacing: {
112
+ '18': '4.5rem',
113
+ '88': '22rem',
114
+ },
115
+ maxWidth: {
116
+ '8xl': '88rem',
117
+ '9xl': '96rem',
118
+ },
119
+ zIndex: {
120
+ '60': '60',
121
+ '70': '70',
122
+ '80': '80',
123
+ '90': '90',
124
+ '100': '100',
125
+ }
126
+ },
127
+ },
128
+ plugins: [],
129
+ darkMode: 'class',
130
+ }
frontend/src/components/Header.tsx ADDED
@@ -0,0 +1,146 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ```typescript
2
+ import React, { useState, useRef, useEffect } from 'react';
3
+ import { Send, Settings, Sparkles, Loader2 } from 'lucide-react';
4
+ import { HeaderProps } from '../types';
5
+
6
+ const Header: React.FC<HeaderProps> = ({
7
+ prompt,
8
+ setPrompt,
9
+ onGenerate,
10
+ loading,
11
+ onSettingsClick
12
+ }) => {
13
+ const textareaRef = useRef<HTMLTextAreaElement>(null);
14
+ const [isExpanded, setIsExpanded] = useState(false);
15
+
16
+ // Auto-resize textarea
17
+ useEffect(() => {
18
+ const textarea = textareaRef.current;
19
+ if (textarea) {
20
+ textarea.style.height = 'auto';
21
+ textarea.style.height = Math.min(textarea.scrollHeight, 120) + 'px';
22
+ }
23
+ }, [prompt]);
24
+
25
+ const handleSubmit = (e: React.FormEvent) => {
26
+ e.preventDefault();
27
+ if (!loading && prompt.trim()) {
28
+ onGenerate();
29
+ }
30
+ };
31
+
32
+ const handleKeyDown = (e: React.KeyboardEvent) => {
33
+ if (e.key === 'Enter' && !e.shiftKey) {
34
+ e.preventDefault();
35
+ handleSubmit(e);
36
+ }
37
+ };
38
+
39
+ const suggestedPrompts = [
40
+ "Create a modern landing page with hero section and features",
41
+ "Build a portfolio website with dark theme",
42
+ "Generate a responsive e-commerce product page",
43
+ "Make a blog with modern typography",
44
+ "Create a dashboard with charts and analytics"
45
+ ];
46
+
47
+ return (
48
+ <header className="header">
49
+ <div className="header-content">
50
+ {/* Logo and Title */}
51
+ <div className="logo-section">
52
+ <div className="logo-icon">
53
+ <Sparkles className="w-8 h-8 text-white" />
54
+ </div>
55
+ <div className="logo-text">
56
+ <h1 className="text-2xl font-bold text-white">DeepSite</h1>
57
+ <p className="text-white/80 text-sm">AI-Powered Website Builder</p>
58
+ </div>
59
+ </div>
60
+
61
+ {/* Main Input */}
62
+ <form onSubmit={handleSubmit} className="input-section">
63
+ <div className="input-container">
64
+ <div className="input-wrapper">
65
+ <textarea
66
+ ref={textareaRef}
67
+ value={prompt}
68
+ onChange={(e) => setPrompt(e.target.value)}
69
+ onKeyDown={handleKeyDown}
70
+ onFocus={() => setIsExpanded(true)}
71
+ onBlur={() => setIsExpanded(false)}
72
+ placeholder="Describe the website you want to create... (e.g., 'Create a modern portfolio with dark theme and smooth animations')"
73
+ className="main-input"
74
+ rows={1}
75
+ disabled={loading}
76
+ />
77
+
78
+ <button
79
+ type="submit"
80
+ disabled={loading || !prompt.trim()}
81
+ className="submit-button"
82
+ title="Generate Website (⌘+Enter)"
83
+ >
84
+ {loading ? (
85
+ <Loader2 className="w-5 h-5 animate-spin" />
86
+ ) : (
87
+ <Send className="w-5 h-5" />
88
+ )}
89
+ </button>
90
+ </div>
91
+
92
+ {/* Suggested Prompts */}
93
+ {isExpanded && (
94
+ <div className="suggestions fade-in">
95
+ <div className="suggestions-header">
96
+ <span className="text-sm text-white/60">Try these:</span>
97
+ </div>
98
+ <div className="suggestions-grid">
99
+ {suggestedPrompts.map((suggestion, index) => (
100
+ <button
101
+ key={index}
102
+ type="button"
103
+ onClick={() => setPrompt(suggestion)}
104
+ className="suggestion-chip"
105
+ disabled={loading}
106
+ >
107
+ {suggestion}
108
+ </button>
109
+ ))}
110
+ </div>
111
+ </div>
112
+ )}
113
+ </div>
114
+ </form>
115
+
116
+ {/* Settings Button */}
117
+ <button
118
+ onClick={onSettingsClick}
119
+ className="settings-button"
120
+ title="Settings"
121
+ >
122
+ <Settings className="w-5 h-5" />
123
+ </button>
124
+ </div>
125
+
126
+ {/* Quick Stats */}
127
+ <div className="header-stats">
128
+ <div className="stat-item">
129
+ <span className="stat-number">∞</span>
130
+ <span className="stat-label">AI Models</span>
131
+ </div>
132
+ <div className="stat-item">
133
+ <span className="stat-number">100%</span>
134
+ <span className="stat-label">Free</span>
135
+ </div>
136
+ <div className="stat-item">
137
+ <span className="stat-number">⚑</span>
138
+ <span className="stat-label">Fast</span>
139
+ </div>
140
+ </div>
141
+ </header>
142
+ );
143
+ };
144
+
145
+ export default Header;
146
+ ```
frontend/src/index.css ADDED
@@ -0,0 +1,198 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ @tailwind base;
2
+ @tailwind components;
3
+ @tailwind utilities;
4
+
5
+ /* Custom CSS Variables */
6
+ :root {
7
+ --primary-50: #f0f9ff;
8
+ --primary-100: #e0f2fe;
9
+ --primary-500: #0ea5e9;
10
+ --primary-600: #0284c7;
11
+ --primary-700: #0369a1;
12
+ --secondary-500: #8b5cf6;
13
+ --success-500: #10b981;
14
+ --warning-500: #f59e0b;
15
+ --error-500: #ef4444;
16
+ --gray-50: #f9fafb;
17
+ --gray-100: #f3f4f6;
18
+ --gray-200: #e5e7eb;
19
+ --gray-300: #d1d5db;
20
+ --gray-400: #9ca3af;
21
+ --gray-500: #6b7280;
22
+ --gray-600: #4b5563;
23
+ --gray-700: #374151;
24
+ --gray-800: #1f2937;
25
+ --gray-900: #111827;
26
+ }
27
+
28
+ /* Reset and base styles */
29
+ * {
30
+ margin: 0;
31
+ padding: 0;
32
+ box-sizing: border-box;
33
+ }
34
+
35
+ html, body, #root {
36
+ height: 100%;
37
+ width: 100%;
38
+ overflow: hidden;
39
+ }
40
+
41
+ body {
42
+ font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
43
+ 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;
44
+ -webkit-font-smoothing: antialiased;
45
+ -moz-osx-font-smoothing: grayscale;
46
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
47
+ color: var(--gray-900);
48
+ }
49
+
50
+ /* Scrollbar styling */
51
+ ::-webkit-scrollbar {
52
+ width: 8px;
53
+ height: 8px;
54
+ }
55
+
56
+ ::-webkit-scrollbar-track {
57
+ background: var(--gray-100);
58
+ border-radius: 4px;
59
+ }
60
+
61
+ ::-webkit-scrollbar-thumb {
62
+ background: var(--gray-300);
63
+ border-radius: 4px;
64
+ transition: background 0.2s;
65
+ }
66
+
67
+ ::-webkit-scrollbar-thumb:hover {
68
+ background: var(--gray-400);
69
+ }
70
+
71
+ /* Firefox scrollbar */
72
+ * {
73
+ scrollbar-width: thin;
74
+ scrollbar-color: var(--gray-300) var(--gray-100);
75
+ }
76
+
77
+ /* Focus styles */
78
+ *:focus {
79
+ outline: 2px solid var(--primary-500);
80
+ outline-offset: 2px;
81
+ }
82
+
83
+ button:focus,
84
+ input:focus,
85
+ textarea:focus,
86
+ select:focus {
87
+ outline: 2px solid var(--primary-500);
88
+ outline-offset: 2px;
89
+ }
90
+
91
+ /* Button reset */
92
+ button {
93
+ cursor: pointer;
94
+ border: none;
95
+ font-family: inherit;
96
+ background: none;
97
+ }
98
+
99
+ button:disabled {
100
+ cursor: not-allowed;
101
+ opacity: 0.6;
102
+ }
103
+
104
+ /* Form elements */
105
+ input, textarea, select {
106
+ font-family: inherit;
107
+ }
108
+
109
+ input::placeholder,
110
+ textarea::placeholder {
111
+ color: var(--gray-400);
112
+ }
113
+
114
+ /* Utility classes */
115
+ .gradient-bg {
116
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
117
+ }
118
+
119
+ .glass-effect {
120
+ background: rgba(255, 255, 255, 0.25);
121
+ backdrop-filter: blur(10px);
122
+ -webkit-backdrop-filter: blur(10px);
123
+ border: 1px solid rgba(255, 255, 255, 0.18);
124
+ }
125
+
126
+ .shadow-glow {
127
+ box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06), 0 0 0 1px rgba(255, 255, 255, 0.05);
128
+ }
129
+
130
+ /* Animation classes */
131
+ .fade-in {
132
+ animation: fadeIn 0.3s ease-in-out;
133
+ }
134
+
135
+ .slide-up {
136
+ animation: slideUp 0.3s ease-out;
137
+ }
138
+
139
+ .scale-in {
140
+ animation: scaleIn 0.2s ease-out;
141
+ }
142
+
143
+ @keyframes fadeIn {
144
+ from { opacity: 0; }
145
+ to { opacity: 1; }
146
+ }
147
+
148
+ @keyframes slideUp {
149
+ from {
150
+ opacity: 0;
151
+ transform: translateY(20px);
152
+ }
153
+ to {
154
+ opacity: 1;
155
+ transform: translateY(0);
156
+ }
157
+ }
158
+
159
+ @keyframes scaleIn {
160
+ from {
161
+ opacity: 0;
162
+ transform: scale(0.95);
163
+ }
164
+ to {
165
+ opacity: 1;
166
+ transform: scale(1);
167
+ }
168
+ }
169
+
170
+ /* Loading spinner */
171
+ .spinner {
172
+ animation: spin 1s linear infinite;
173
+ }
174
+
175
+ @keyframes spin {
176
+ from { transform: rotate(0deg); }
177
+ to { transform: rotate(360deg); }
178
+ }
179
+
180
+ /* Responsive design helpers */
181
+ @media (max-width: 768px) {
182
+ .mobile-hidden {
183
+ display: none !important;
184
+ }
185
+ }
186
+
187
+ @media (min-width: 769px) {
188
+ .mobile-only {
189
+ display: none !important;
190
+ }
191
+ }
192
+
193
+ /* Print styles */
194
+ @media print {
195
+ body {
196
+ background: white !important;
197
+ }
198
+ }
frontend/src/index.tsx ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ```typescript
2
+ import React from 'react';
3
+ import ReactDOM from 'react-dom/client';
4
+ import './index.css';
5
+ import App from './App';
6
+
7
+ const root = ReactDOM.createRoot(
8
+ document.getElementById('root') as HTMLElement
9
+ );
10
+
11
+ root.render(
12
+ <React.StrictMode>
13
+ <App />
14
+ </React.StrictMode>
15
+ );
16
+ ```
frontend/src/services/api.ts ADDED
@@ -0,0 +1,230 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ```typescript
2
+ import axios, { AxiosResponse, AxiosError } from 'axios';
3
+ import { GenerateRequest, GenerateResponse, ApiError, ApiStatus, ModelInfo, ApiProvider } from '../types';
4
+
5
+ const API_BASE_URL = process.env.REACT_APP_API_URL || 'http://localhost:5000';
6
+ const API_TIMEOUT = 30000; // 30 seconds
7
+
8
+ class ApiServiceClass {
9
+ private axiosInstance = axios.create({
10
+ baseURL: API_BASE_URL,
11
+ timeout: API_TIMEOUT,
12
+ headers: {
13
+ 'Content-Type': 'application/json',
14
+ },
15
+ });
16
+
17
+ constructor() {
18
+ // Request interceptor
19
+ this.axiosInstance.interceptors.request.use(
20
+ (config) => {
21
+ console.log(`API Request: ${config.method?.toUpperCase()} ${config.url}`);
22
+ return config;
23
+ },
24
+ (error) => {
25
+ console.error('Request Error:', error);
26
+ return Promise.reject(error);
27
+ }
28
+ );
29
+
30
+ // Response interceptor
31
+ this.axiosInstance.interceptors.response.use(
32
+ (response) => {
33
+ console.log(`API Response: ${response.status} ${response.config.url}`);
34
+ return response;
35
+ },
36
+ (error: AxiosError) => {
37
+ console.error('Response Error:', error.response?.status, error.response?.data);
38
+
39
+ // Transform axios errors to our custom error type
40
+ const apiError = new ApiError(
41
+ this.getErrorMessage(error),
42
+ error.code,
43
+ error.response?.status,
44
+ error.response?.data
45
+ );
46
+
47
+ return Promise.reject(apiError);
48
+ }
49
+ );
50
+ }
51
+
52
+ private getErrorMessage(error: AxiosError): string {
53
+ if (error.response?.data) {
54
+ const data = error.response.data as any;
55
+ if (data.error) {
56
+ return typeof data.error === 'string' ? data.error : data.error.message;
57
+ }
58
+ if (data.message) {
59
+ return data.message;
60
+ }
61
+ }
62
+
63
+ if (error.request) {
64
+ return 'Network error: Unable to connect to server';
65
+ }
66
+
67
+ return error.message || 'An unknown error occurred';
68
+ }
69
+
70
+ // Health check
71
+ async healthCheck(): Promise<ApiStatus> {
72
+ try {
73
+ const response = await this.axiosInstance.get('/api/health');
74
+ const data = response.data;
75
+
76
+ return {
77
+ status: 'online',
78
+ version: data.version,
79
+ latency: response.headers['x-response-time'] ?
80
+ parseInt(response.headers['x-response-time'] as string) : undefined,
81
+ uptime: data.uptime
82
+ };
83
+ } catch (error) {
84
+ throw new ApiError('Health check failed', 'HEALTH_CHECK_FAILED');
85
+ }
86
+ }
87
+
88
+ // Generate website code
89
+ async generate(request: GenerateRequest): Promise<GenerateResponse> {
90
+ try {
91
+ const response = await this.axiosInstance.post<GenerateResponse>(
92
+ '/api/generate',
93
+ {
94
+ prompt: request.prompt,
95
+ provider: request.provider,
96
+ model: request.model,
97
+ temperature: request.temperature || 0.7,
98
+ max_tokens: request.maxTokens || 16000
99
+ },
100
+ {
101
+ headers: request.apiKey ? {
102
+ 'Authorization': `Bearer ${request.apiKey}`
103
+ } : undefined
104
+ }
105
+ );
106
+
107
+ return response.data;
108
+ } catch (error) {
109
+ if (error instanceof ApiError) {
110
+ throw error;
111
+ }
112
+ throw new ApiError('Generation failed', 'GENERATION_FAILED');
113
+ }
114
+ }
115
+
116
+ // Validate API key
117
+ async validateKey(provider: ApiProvider, apiKey: string): Promise<{ valid: boolean; error?: string }> {
118
+ try {
119
+ const response = await this.axiosInstance.post('/api/validate-key', {
120
+ provider,
121
+ apiKey
122
+ });
123
+
124
+ return {
125
+ valid: response.data.valid,
126
+ error: response.data.error
127
+ };
128
+ } catch (error) {
129
+ return {
130
+ valid: false,
131
+ error: error instanceof ApiError ? error.message : 'Validation failed'
132
+ };
133
+ }
134
+ }
135
+
136
+ // Get available models for a provider
137
+ async getModels(provider: ApiProvider): Promise<ModelInfo[]> {
138
+ try {
139
+ const response = await this.axiosInstance.get(`/api/models/${provider}`);
140
+
141
+ const models = response.data.models || [];
142
+ return models.map((model: string | any) => {
143
+ if (typeof model === 'string') {
144
+ return {
145
+ id: model,
146
+ name: model,
147
+ contextLength: 4096
148
+ };
149
+ }
150
+ return {
151
+ id: model.id || model.name || model,
152
+ name: model.name || model.id || model,
153
+ description: model.description,
154
+ contextLength: model.context_length || model.contextLength,
155
+ pricing: model.pricing
156
+ };
157
+ });
158
+ } catch (error) {
159
+ console.error('Failed to fetch models:', error);
160
+ return [];
161
+ }
162
+ }
163
+
164
+ // Test API connectivity
165
+ async testConnection(provider: ApiProvider, config: { apiKey?: string; baseUrl?: string }): Promise<boolean> {
166
+ try {
167
+ // For now, just try to validate the key
168
+ if (config.apiKey) {
169
+ const validation = await this.validateKey(provider, config.apiKey);
170
+ return validation.valid;
171
+ }
172
+ return false;
173
+ } catch (error) {
174
+ console.error('Connection test failed:', error);
175
+ return false;
176
+ }
177
+ }
178
+
179
+ // Rate limiting helpers
180
+ private lastRequestTime = 0;
181
+ private readonly MIN_REQUEST_INTERVAL = 1000; // 1 second
182
+
183
+ private async enforceRateLimit(): Promise<void> {
184
+ const now = Date.now();
185
+ const timeSinceLastRequest = now - this.lastRequestTime;
186
+
187
+ if (timeSinceLastRequest < this.MIN_REQUEST_INTERVAL) {
188
+ const waitTime = this.MIN_REQUEST_INTERVAL - timeSinceLastRequest;
189
+ await new Promise(resolve => setTimeout(resolve, waitTime));
190
+ }
191
+
192
+ this.lastRequestTime = Date.now();
193
+ }
194
+
195
+ // Wrapped generation with rate limiting
196
+ async generateWithRateLimit(request: GenerateRequest): Promise<GenerateResponse> {
197
+ await this.enforceRateLimit();
198
+ return this.generate(request);
199
+ }
200
+
201
+ // Error recovery
202
+ private retryCount = new Map<string, number>();
203
+ private readonly MAX_RETRIES = 3;
204
+
205
+ async generateWithRetry(request: GenerateRequest): Promise<GenerateResponse> {
206
+ const requestKey = `${request.provider}-${request.model}-${Date.now()}`;
207
+ const retries = this.retryCount.get(requestKey) || 0;
208
+
209
+ try {
210
+ this.retryCount.set(requestKey, retries + 1);
211
+ return await this.generate(request);
212
+ } catch (error) {
213
+ // Retry on network errors or 5xx status codes
214
+ if (retries < this.MAX_RETRIES &&
215
+ (error instanceof ApiError &&
216
+ (error.status === undefined || error.status >= 500 || error.status === 0))) {
217
+ console.log(`Retrying request (attempt ${retries + 1}/${this.MAX_RETRIES})`);
218
+ await new Promise(resolve => setTimeout(resolve, Math.pow(2, retries) * 1000)); // Exponential backoff
219
+ return this.generateWithRetry(request);
220
+ }
221
+ throw error;
222
+ } finally {
223
+ this.retryCount.delete(requestKey);
224
+ }
225
+ }
226
+ }
227
+
228
+ export const ApiService = new ApiServiceClass();
229
+ export default ApiService;
230
+ ```
frontend/src/types/index.ts ADDED
@@ -0,0 +1,246 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ```typescript
2
+ // Main types for the application
3
+
4
+ export interface ChatMessage {
5
+ id: string;
6
+ role: 'user' | 'assistant';
7
+ content: string;
8
+ timestamp: Date;
9
+ metadata?: {
10
+ model?: string;
11
+ provider?: string;
12
+ tokens?: number;
13
+ };
14
+ }
15
+
16
+ export interface AppSettings {
17
+ provider: ApiProvider;
18
+ apiKey: string;
19
+ model: string;
20
+ theme: 'light' | 'dark' | 'auto';
21
+ language: string;
22
+ autoSave: boolean;
23
+ showLineNumbers: boolean;
24
+ fontSize: number;
25
+ }
26
+
27
+ export type ApiProvider = 'openai' | 'anthropic' | 'nvidia' | 'openrouter';
28
+
29
+ export interface GenerateRequest {
30
+ prompt: string;
31
+ provider: ApiProvider;
32
+ model: string;
33
+ apiKey?: string;
34
+ temperature?: number;
35
+ maxTokens?: number;
36
+ }
37
+
38
+ export interface GenerateResponse {
39
+ code: string;
40
+ provider: ApiProvider;
41
+ model: string;
42
+ timestamp: string;
43
+ usage?: {
44
+ promptTokens: number;
45
+ completionTokens: number;
46
+ totalTokens: number;
47
+ };
48
+ }
49
+
50
+ export interface ApiError {
51
+ message: string;
52
+ code?: string;
53
+ status?: number;
54
+ }
55
+
56
+ export interface ModelInfo {
57
+ id: string;
58
+ name: string;
59
+ description?: string;
60
+ contextLength?: number;
61
+ pricing?: {
62
+ prompt: number;
63
+ completion: number;
64
+ };
65
+ }
66
+
67
+ export interface ApiStatus {
68
+ status: 'online' | 'offline' | 'checking';
69
+ latency?: number;
70
+ version?: string;
71
+ uptime?: number;
72
+ }
73
+
74
+ export interface PreviewOptions {
75
+ responsive: boolean;
76
+ viewport: 'desktop' | 'tablet' | 'mobile';
77
+ darkMode: boolean;
78
+ zoom: number;
79
+ }
80
+
81
+ export interface ExportOptions {
82
+ format: 'html' | 'zip' | 'pdf';
83
+ includeAssets: boolean;
84
+ minify: boolean;
85
+ }
86
+
87
+ export interface ProjectMetadata {
88
+ id: string;
89
+ name: string;
90
+ description?: string;
91
+ tags: string[];
92
+ createdAt: Date;
93
+ updatedAt: Date;
94
+ version: string;
95
+ author?: string;
96
+ }
97
+
98
+ export interface Template {
99
+ id: string;
100
+ name: string;
101
+ description: string;
102
+ category: string;
103
+ preview: string;
104
+ code: string;
105
+ tags: string[];
106
+ difficulty: 'beginner' | 'intermediate' | 'advanced';
107
+ }
108
+
109
+ // Component Props Types
110
+ export interface HeaderProps {
111
+ prompt: string;
112
+ setPrompt: (prompt: string) => void;
113
+ onGenerate: () => void;
114
+ loading: boolean;
115
+ onSettingsClick: () => void;
116
+ }
117
+
118
+ export interface ChatPanelProps {
119
+ messages: ChatMessage[];
120
+ onClear?: () => void;
121
+ onExport?: (format: 'json' | 'txt') => void;
122
+ }
123
+
124
+ export interface PreviewPanelProps {
125
+ generatedCode: string;
126
+ error: string;
127
+ success: string;
128
+ loading: boolean;
129
+ onExport?: (options: ExportOptions) => void;
130
+ onCopy?: () => void;
131
+ onDownload?: () => void;
132
+ }
133
+
134
+ export interface SettingsModalProps {
135
+ settings: AppSettings;
136
+ onSettingsChange: (settings: AppSettings) => void;
137
+ onClose: () => void;
138
+ onApiStatusCheck: () => void;
139
+ }
140
+
141
+ // Utility Types
142
+ export type DeepPartial<T> = {
143
+ [P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P];
144
+ };
145
+
146
+ export type RequiredKeys<T> = {
147
+ [K in keyof T]-?: {} extends Pick<T, K> ? never : K;
148
+ }[keyof T];
149
+
150
+ export type OptionalKeys<T> = {
151
+ [K in keyof T]-?: {} extends Pick<T, K> ? K : never;
152
+ }[keyof T];
153
+
154
+ // Error Types
155
+ export class ApiError extends Error {
156
+ constructor(
157
+ message: string,
158
+ public code?: string,
159
+ public status?: number,
160
+ public details?: any
161
+ ) {
162
+ super(message);
163
+ this.name = 'ApiError';
164
+ }
165
+ }
166
+
167
+ export class GenerationError extends Error {
168
+ constructor(
169
+ message: string,
170
+ public provider: ApiProvider,
171
+ public originalError?: any
172
+ ) {
173
+ super(message);
174
+ this.name = 'GenerationError';
175
+ }
176
+ }
177
+
178
+ // Configuration Types
179
+ export interface AppConfig {
180
+ api: {
181
+ baseUrl: string;
182
+ timeout: number;
183
+ retries: number;
184
+ };
185
+ ui: {
186
+ theme: string;
187
+ animations: boolean;
188
+ compactMode: boolean;
189
+ };
190
+ features: {
191
+ analytics: boolean;
192
+ errorReporting: boolean;
193
+ autoSave: boolean;
194
+ templates: boolean;
195
+ };
196
+ }
197
+
198
+ // Local Storage Keys
199
+ export const STORAGE_KEYS = {
200
+ SETTINGS: 'deepsite-settings',
201
+ CHAT_HISTORY: 'deepsite-chat-history',
202
+ PROJECTS: 'deepsite-projects',
203
+ TEMPLATES: 'deepsite-templates',
204
+ ANALYTICS: 'deepsite-analytics'
205
+ } as const;
206
+
207
+ // Constants
208
+ export const API_PROVIDERS = {
209
+ openai: {
210
+ name: 'OpenAI',
211
+ models: ['gpt-4', 'gpt-4-turbo', 'gpt-3.5-turbo'],
212
+ requiresKey: true
213
+ },
214
+ anthropic: {
215
+ name: 'Anthropic',
216
+ models: ['claude-3-opus-20240229', 'claude-3-sonnet-20240229', 'claude-3-haiku-20240307'],
217
+ requiresKey: true
218
+ },
219
+ nvidia: {
220
+ name: 'NVIDIA NIM',
221
+ models: ['meta/llama-2-70b', 'meta/codellama-70b', 'mistralai/mixtral-8x7b-instruct-v0.1'],
222
+ requiresKey: true
223
+ },
224
+ openrouter: {
225
+ name: 'OpenRouter',
226
+ models: ['openai/gpt-4', 'anthropic/claude-3-haiku', 'meta-llama/llama-2-70b-chat'],
227
+ requiresKey: true
228
+ }
229
+ } as const;
230
+
231
+ export const THEMES = {
232
+ light: 'Light',
233
+ dark: 'Dark',
234
+ auto: 'System'
235
+ } as const;
236
+
237
+ export const LANGUAGES = {
238
+ en: 'English',
239
+ es: 'EspaΓ±ol',
240
+ fr: 'FranΓ§ais',
241
+ de: 'Deutsch',
242
+ zh: 'δΈ­ζ–‡',
243
+ ja: 'ζ—₯本θͺž',
244
+ ko: 'ν•œκ΅­μ–΄'
245
+ } as const;
246
+ ```
index.html CHANGED
@@ -1,19 +1,229 @@
1
- <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
  </html>
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
6
+ <title>DeepSite NIM</title>
7
+ <link rel="icon" href="data:;base64,iVBORw0KGgo=" />
8
+ <link rel="stylesheet" href="style.css" />
9
+ <script src="https://cdn.tailwindcss.com"></script>
10
+ <script src="https://cdn.jsdelivr.net/npm/@huggingface/inference@2.6.4"></script>
11
+ <script>
12
+ tailwind.config = {
13
+ darkMode: 'class',
14
+ theme: {
15
+ extend: {
16
+ colors: {
17
+ primary: {
18
+ 50: '#eef2ff',
19
+ 100: '#e0e7ff',
20
+ 200: '#c7d2fe',
21
+ 300: '#a5b4fc',
22
+ 400: '#818cf8',
23
+ 500: '#6366f1',
24
+ 600: '#4f46e5',
25
+ 700: '#4338ca',
26
+ 800: '#3730a3',
27
+ 900: '#312e81'
28
+ },
29
+ secondary: {
30
+ 50: '#f0f9ff',
31
+ 100: '#e0f2fe',
32
+ 200: '#bae6fd',
33
+ 300: '#7dd3fc',
34
+ 400: '#38bdf8',
35
+ 500: '#0ea5e9',
36
+ 600: '#0284c7',
37
+ 700: '#0369a1',
38
+ 800: '#075985',
39
+ 900: '#0c4a6e'
40
+ },
41
+ neon: {
42
+ 50: '#f0fdf4',
43
+ 100: '#dcfce7',
44
+ 200: '#bbf7d0',
45
+ 300: '#86efac',
46
+ 400: '#4ade80',
47
+ 500: '#22c55e',
48
+ 600: '#16a34a',
49
+ 700: '#15803d',
50
+ 800: '#166534',
51
+ 900: '#14532d'
52
+ }
53
+ }
54
+ }
55
+ }
56
+ }
57
+ </script>
58
+ <script>
59
+ // Keep icons available globally
60
+ window.feather = undefined; // Placeholder to avoid global leaks
61
+ </script>
62
+ </head>
63
+ <body class="bg-white text-gray-900">
64
+ <div class="flex h-screen overflow-hidden">
65
+ <!-- Sidebar -->
66
+ <aside class="hidden md:flex md:flex-col w-72 border-r border-gray-200 bg-gray-50">
67
+ <div class="h-16 flex items-center px-4 border-b border-gray-200">
68
+ <div class="flex items-center gap-2">
69
+ <div class="w-8 h-8 rounded bg-primary-500 text-white flex items-center justify-center font-bold">DS</div>
70
+ <div class="font-semibold">DeepSite NIM</div>
71
+ </div>
72
+ </div>
73
+ <div class="flex-1 overflow-y-auto p-3 space-y-2">
74
+ <button id="new-chat" class="w-full flex items-center gap-2 px-3 py-2 rounded-xl border border-gray-200/50 bg-white/50 backdrop-blur-sm hover:bg-white/80 hover:border-primary-300 transition-all duration-200 font-medium">
75
+ <svg xmlns="http://www.w3.org/2000/svg" class="w-4 h-4 text-secondary-600" viewBox="0 0 24 24" fill="none" stroke="currentColor"><path stroke-width="2" d="M12 5v14M5 12h14"/></svg>
76
+ New Chat
77
+ </button>
78
+ <div class="pt-2">
79
+ <div class="text-xs uppercase tracking-wider text-gray-500 px-2 mb-1">Recent</div>
80
+ <ul id="chat-list" class="space-y-1"></ul>
81
+ </div>
82
+ </div>
83
+ <div class="p-3 border-t border-gray-200">
84
+ <div class="space-y-2">
85
+ <div>
86
+ <label class="block text-xs font-medium text-gray-600 mb-1">Hugging Face Token (Optional)</label>
87
+ <input id="hf-token" type="password" placeholder="hf_..." class="w-full rounded-xl border border-gray-200/50 bg-white/50 backdrop-blur-sm px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-primary-400 transition-all duration-200" />
88
+ <p class="text-[11px] text-gray-500 mt-1">For private models. Not required for public models.</p>
89
+ </div>
90
+ <div>
91
+ <label class="block text-xs font-medium text-gray-600 mb-1">Local Model</label>
92
+ <div class="space-y-2">
93
+ <select id="model-select" class="w-full rounded-xl border border-gray-200/50 bg-white/50 backdrop-blur-sm px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-primary-400 transition-all duration-200">
94
+ <option value="microsoft/DialoGPT-medium">microsoft/DialoGPT-medium (2.5GB)</option>
95
+ <option value="microsoft/DialoGPT-large">microsoft/DialoGPT-large (3.5GB)</option>
96
+ <option value="mistralai/Mistral-7B-Instruct-v0.1">mistralai/Mistral-7B-Instruct-v0.1 (7GB)</option>
97
+ <option value="TheBloke/CodeLlama-7B-Instruct-GGUF">TheBloke/CodeLlama-7B-Instruct-GGUF (4GB)</option>
98
+ <option value="local-upload">πŸ“ Upload from Computer</option>
99
+ </select>
100
+ <div id="local-model-upload" class="hidden">
101
+ <input type="file" id="model-file-input" accept=".bin,.safetensors,.gguf,.ggml,.pt,.pth" class="hidden" />
102
+ <button id="upload-model-btn" class="w-full flex items-center justify-center gap-2 px-3 py-2 rounded-xl border-2 border-dashed border-gray-300 hover:border-primary-400 hover:bg-primary-50/50 transition-all duration-200 text-sm font-medium">
103
+ <svg xmlns="http://www.w3.org/2000/svg" class="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor">
104
+ <path stroke-width="2" d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12"/>
105
+ </svg>
106
+ Choose Model File
107
+ </button>
108
+ <p id="selected-model-name" class="text-[11px] text-gray-600 mt-1 truncate"></p>
109
+ </div>
110
+ </div>
111
+ <p class="text-[11px] text-gray-500 mt-1">Downloads on first use or upload your own model files.</p>
112
+ </div>
113
+ <div>
114
+ <label class="block text-xs font-medium text-gray-600 mb-1">Template</label>
115
+ <select id="template-select" class="w-full rounded-xl border border-gray-200/50 bg-white/50 backdrop-blur-sm px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-primary-400 transition-all duration-200">
116
+ <option value="vibecode">πŸš€ VibeCode - Web Apps</option>
117
+ <option value="website">🌐 Clean Website</option>
118
+ <option value="landing">🎯 Landing Page</option>
119
+ <option value="portfolio">πŸ’Ό Portfolio</option>
120
+ <option value="saas">πŸ“Š SaaS Dashboard</option>
121
+ <option value="ecommerce">πŸ›’ E-commerce</option>
122
+ <option value="blog">πŸ“ Blog/CMS</option>
123
+ </select>
124
+ </div>
125
+ </div>
126
+ </div>
127
+ </aside>
128
+
129
+ <!-- Main -->
130
+ <main class="flex-1 flex flex-col">
131
+ <!-- Top bar -->
132
+ <header class="h-16 border-b border-gray-200 flex items-center justify-between px-4 bg-white">
133
+ <div class="flex items-center gap-3">
134
+ <button id="mobile-menu-btn" class="md:hidden inline-flex items-center justify-center w-9 h-9 rounded-xl border border-gray-200/50 bg-white/50 backdrop-blur-sm hover:bg-white/80 hover:border-primary-300 transition-all duration-200" title="Toggle sidebar">
135
+ <svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5" viewBox="0 0 24 24" fill="none" stroke="currentColor"><path stroke-width="2" d="M3 6h18M3 12h18M3 18h18"/></svg>
136
+ </button>
137
+ <div class="font-semibold text-lg">DeepSite NIM</div>
138
+ </div>
139
+ <div class="flex items-center gap-2">
140
+ <button id="export-chat" class="inline-flex items-center gap-2 px-3 py-1.5 rounded-xl border border-gray-200/50 bg-white/50 backdrop-blur-sm hover:bg-white/80 hover:border-primary-300 transition-all duration-200 text-sm font-medium">
141
+ <svg xmlns="http://www.w3.org/2000/svg" class="w-4 h-4 text-secondary-600" viewBox="0 0 24 24" fill="none" stroke="currentColor"><path stroke-width="2" d="M12 3v12m0 0l-4-4m4 4l4-4M4 21h16"/></svg>
142
+ Export
143
+ </button>
144
+ </div>
145
+ </header>
146
+ <!-- Messages -->
147
+ <div id="messages" class="flex-1 overflow-y-auto p-4 space-y-4">
148
+ <div id="welcome" class="max-w-3xl mx-auto text-center mt-10">
149
+ <div class="inline-flex items-center gap-2 px-4 py-2 rounded-full bg-gradient-to-r from-primary-50 to-secondary-50 text-primary-700 text-sm border border-primary-200/50 backdrop-blur-sm">
150
+ ✨ Powered by Local AI
151
+ </div>
152
+ <h1 class="text-4xl font-bold mb-3 bg-gradient-to-r from-gray-800 to-gray-600 bg-clip-text text-transparent">What can we build today?</h1>
153
+ <p class="text-gray-600 text-lg">Describe your web app, website, or idea. We'll generate code and show you a live preview.</p>
154
+ <div class="mt-8 grid grid-cols-2 md:grid-cols-3 gap-4">
155
+ <button onclick="quickTemplate('vibecode')" class="group p-4 rounded-2xl border border-gray-200/50 bg-white/30 backdrop-blur-sm hover:bg-white/60 hover:border-primary-300 hover:shadow-lg transition-all duration-300 text-left relative overflow-hidden">
156
+ <div class="absolute inset-0 bg-gradient-to-br from-primary-500/10 to-secondary-500/10 opacity-0 group-hover:opacity-100 transition-opacity duration-300"></div>
157
+ <div class="relative">
158
+ <div class="font-semibold text-sm mb-1">πŸš€ VibeCode App</div>
159
+ <div class="text-xs text-gray-500">Modern web app with vibecoding</div>
160
+ </div>
161
+ </button>
162
+ <button onclick="quickTemplate('website')" class="group p-4 rounded-2xl border border-gray-200/50 bg-white/30 backdrop-blur-sm hover:bg-white/60 hover:border-primary-300 hover:shadow-lg transition-all duration-300 text-left relative overflow-hidden">
163
+ <div class="absolute inset-0 bg-gradient-to-br from-blue-500/10 to-purple-500/10 opacity-0 group-hover:opacity-100 transition-opacity duration-300"></div>
164
+ <div class="relative">
165
+ <div class="font-semibold text-sm mb-1">🌐 Clean Website</div>
166
+ <div class="text-xs text-gray-500">Professional business site</div>
167
+ </div>
168
+ </button>
169
+ <button onclick="quickTemplate('saas')" class="group p-4 rounded-2xl border border-gray-200/50 bg-white/30 backdrop-blur-sm hover:bg-white/60 hover:border-primary-300 hover:shadow-lg transition-all duration-300 text-left relative overflow-hidden">
170
+ <div class="absolute inset-0 bg-gradient-to-br from-green-500/10 to-teal-500/10 opacity-0 group-hover:opacity-100 transition-opacity duration-300"></div>
171
+ <div class="relative">
172
+ <div class="font-semibold text-sm mb-1">πŸ“Š SaaS Dashboard</div>
173
+ <div class="text-xs text-gray-500">Analytics & admin panel</div>
174
+ </div>
175
+ </button>
176
+ </div>
177
+ </div>
178
+ </div>
179
+
180
+ <!-- Preview Panel (Initially Hidden) -->
181
+ <div id="preview-panel" class="hidden border-t border-gray-200 bg-gray-900">
182
+ <div class="h-80 flex flex-col">
183
+ <div class="flex items-center justify-between px-4 py-2 bg-gray-800 border-b border-gray-700">
184
+ <div class="flex items-center gap-2">
185
+ <div class="w-3 h-3 rounded-full bg-red-500"></div>
186
+ <div class="w-3 h-3 rounded-full bg-yellow-500"></div>
187
+ <div class="w-3 h-3 rounded-full bg-green-500"></div>
188
+ <span class="text-gray-300 text-sm ml-3">Live Preview</span>
189
+ </div>
190
+ <div class="flex items-center gap-2">
191
+ <button id="refresh-preview" class="text-xs text-gray-400 hover:text-white">Refresh</button>
192
+ <button id="open-preview" class="text-xs text-gray-400 hover:text-white">Open</button>
193
+ <button id="close-preview" class="text-xs text-gray-400 hover:text-white">Close</button>
194
+ </div>
195
+ </div>
196
+ <iframe id="preview-frame" class="flex-1 w-full bg-white rounded-b-xl" sandbox="allow-scripts allow-same-origin"></iframe>
197
+ </div>
198
+ </div>
199
+ <!-- Composer -->
200
+ <div class="border-t border-gray-200 bg-gray-50 p-4">
201
+ <div class="max-w-3xl mx-auto">
202
+ <div class="rounded-xl border border-gray-200 bg-white shadow-sm">
203
+ <textarea id="prompt" rows="1" placeholder="Describe what you want to build..." class="w-full resize-none rounded-t-xl px-4 py-3 outline-none placeholder:text-gray-400"></textarea>
204
+ <div class="flex items-center justify-between px-3 py-2">
205
+ <div class="flex items-center gap-2 text-xs text-gray-500">
206
+ <span id="status-text">Ready</span>
207
+ <span id="model-status"></span>
208
+ </div>
209
+ <div class="flex items-center gap-2">
210
+ <button id="stop-btn" class="hidden inline-flex items-center gap-2 px-3 py-1.5 rounded-md border border-gray-200 hover:bg-gray-100 text-sm">
211
+ <svg xmlns="http://www.w3.org/2000/svg" class="w-4 h-4 text-red-600" viewBox="0 0 24 24" fill="none" stroke="currentColor"><path stroke-width="2" d="M6 6h12v12H6z"/></svg>
212
+ Stop
213
+ </button>
214
+ <button id="send-btn" class="inline-flex items-center gap-2 px-4 py-1.5 rounded-md bg-primary-600 text-white hover:bg-primary-700 text-sm">
215
+ <svg xmlns="http://www.w3.org/2000/svg" class="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor"><path stroke-width="2" d="M22 2L11 13"/><path stroke-width="2" d="M22 2l-7 20-4-9-9-4 20-7z"/></svg>
216
+ Generate
217
+ </button>
218
+ </div>
219
+ </div>
220
+ </div>
221
+ <p class="text-[11px] text-gray-500 mt-2">AI runs locally in your browser. No data leaves your device.</p>
222
+ </div>
223
+ </div>
224
+ </main>
225
+ </div>
226
+ <script src="https://huggingface.co/deepsite/deepsite-badge.js"></script>
227
+ <script src="https://huggingface.co/deepsite/deepsite-badge.js"></script>
228
+ </body>
229
  </html>
package.json ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ json
2
+ {
3
+ "name": "vibecode-local",
4
+ "version": "2.0.0",
5
+ "description": "Local AI-powered code generation tool for web apps, websites, and applications with live preview",
6
+ "main": "server.js",
7
+ "scripts": {
8
+ "start": "node server.js",
9
+ "dev": "node server.js"
10
+ },
11
+ "keywords": [
12
+ "ai",
13
+ "code-generation",
14
+ "local-ai",
15
+ "web-development",
16
+ "vibecode",
17
+ "preview",
18
+ "huggingface"
19
+ ],
20
+ "author": "DeepSite Community",
21
+ "license": "MIT",
22
+ "dependencies": {
23
+ "cors": "^2.8.5",
24
+ "express": "^4.19.2"
25
+ },
26
+ "devDependencies": {},
27
+ "engines": {
28
+ "node": ">=14.0.0"
29
+ }
30
+ }
31
+ </html>
script.js ADDED
@@ -0,0 +1,835 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ (() => {
3
+ // State
4
+ let chats = loadChats();
5
+ let currentChatId = null;
6
+ let abortController = null;
7
+ let currentModel = null;
8
+ let modelLoading = false;
9
+ let previewVisible = false;
10
+ let generatedHTML = '';
11
+ let selectedLocalModel = null;
12
+ // Templates for different project types
13
+ const templates = {
14
+ vibecode: {
15
+ name: "VibeCode App",
16
+ description: "Modern web app with vibecoding aesthetics",
17
+ structure: {
18
+ html: `<!DOCTYPE html>
19
+ <html lang="en">
20
+ <head>
21
+ <meta charset="UTF-8">
22
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
23
+ <title>VibeApp</title>
24
+ <script src="https://cdn.tailwindcss.com"></script>
25
+ <style>
26
+ .gradient-bg { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); }
27
+ .glass { backdrop-filter: blur(16px) saturate(180%); background-color: rgba(255, 255, 255, 0.75); }
28
+ .neon { text-shadow: 0 0 10px currentColor; }
29
+ </style>
30
+ </head>
31
+ <body class="gradient-bg min-h-screen">
32
+ <div class="container mx-auto px-4 py-8">
33
+ <header class="text-center mb-12">
34
+ <h1 class="text-4xl font-bold text-white mb-4 neon">VibeApp</h1>
35
+ <p class="text-white/80">Modern vibes, clean code, smooth UX</p>
36
+ </header>
37
+
38
+ <div class="grid md:grid-cols-3 gap-6">
39
+ <div class="glass rounded-lg p-6 shadow-lg">
40
+ <h3 class="text-lg font-semibold mb-3">Dashboard</h3>
41
+ <p class="text-sm opacity-80">Clean overview of your data</p>
42
+ </div>
43
+ <div class="glass rounded-lg p-6 shadow-lg">
44
+ <h3 class="text-lg font-semibold mb-3">Analytics</h3>
45
+ <p class="text-sm opacity-80">Real-time insights</p>
46
+ </div>
47
+ <div class="glass rounded-lg p-6 shadow-lg">
48
+ <h3 class="text-lg font-semibold mb-3">Settings</h3>
49
+ <p class="text-sm opacity-80">Configure your vibe</p>
50
+ </div>
51
+ </div>
52
+ </div>
53
+ </body>
54
+ </html>`,
55
+ js: `// VibeApp JavaScript
56
+ console.log('VibeApp loaded!');
57
+ document.addEventListener('DOMContentLoaded', function() {
58
+ // Add smooth animations
59
+ const cards = document.querySelectorAll('.glass');
60
+ cards.forEach((card, index) => {
61
+ card.style.opacity = '0';
62
+ card.style.transform = 'translateY(20px)';
63
+ setTimeout(() => {
64
+ card.style.transition = 'all 0.5s ease';
65
+ card.style.opacity = '1';
66
+ card.style.transform = 'translateY(0)';
67
+ }, index * 100);
68
+ });
69
+ });`
70
+ }
71
+ },
72
+ website: {
73
+ name: "Clean Website",
74
+ description: "Professional business website",
75
+ structure: {
76
+ html: `<!DOCTYPE html>
77
+ <html lang="en">
78
+ <head>
79
+ <meta charset="UTF-8">
80
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
81
+ <title>Professional Website</title>
82
+ <script src="https://cdn.tailwindcss.com"></script>
83
+ </head>
84
+ <body class="bg-white">
85
+ <nav class="bg-white shadow-sm border-b">
86
+ <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
87
+ <div class="flex justify-between h-16">
88
+ <div class="flex items-center">
89
+ <h1 class="text-xl font-bold text-gray-900">YourBrand</h1>
90
+ </div>
91
+ <div class="hidden md:flex items-center space-x-8">
92
+ <a href="#" class="text-gray-600 hover:text-gray-900">Home</a>
93
+ <a href="#" class="text-gray-600 hover:text-gray-900">About</a>
94
+ <a href="#" class="text-gray-600 hover:text-gray-900">Services</a>
95
+ <a href="#" class="text-gray-600 hover:text-gray-900">Contact</a>
96
+ </div>
97
+ </div>
98
+ </div>
99
+ </nav>
100
+
101
+ <main>
102
+ <section class="bg-gradient-to-r from-blue-600 to-purple-600 text-white">
103
+ <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-24">
104
+ <div class="text-center">
105
+ <h1 class="text-5xl font-bold mb-6">Professional Solutions</h1>
106
+ <p class="text-xl mb-8">We create amazing digital experiences</p>
107
+ <button class="bg-white text-blue-600 px-8 py-3 rounded-lg font-semibold hover:bg-gray-100 transition">
108
+ Get Started
109
+ </button>
110
+ </div>
111
+ </div>
112
+ </section>
113
+ </main>
114
+ </body>
115
+ </html>`,
116
+ js: `// Professional Website JavaScript
117
+ document.addEventListener('DOMContentLoaded', function() {
118
+ console.log('Website loaded successfully');
119
+ });`
120
+ }
121
+ },
122
+ saas: {
123
+ name: "SaaS Dashboard",
124
+ description: "Analytics & admin panel",
125
+ structure: {
126
+ html: `<!DOCTYPE html>
127
+ <html lang="en">
128
+ <head>
129
+ <meta charset="UTF-8">
130
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
131
+ <title>SaaS Dashboard</title>
132
+ <script src="https://cdn.tailwindcss.com"></script>
133
+ <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
134
+ </head>
135
+ <body class="bg-gray-50">
136
+ <div class="min-h-screen flex">
137
+ <!-- Sidebar -->
138
+ <div class="w-64 bg-white shadow-sm">
139
+ <div class="p-6">
140
+ <h2 class="text-xl font-bold text-gray-800">Dashboard</h2>
141
+ </div>
142
+ <nav class="px-4 space-y-2">
143
+ <a href="#" class="block px-3 py-2 text-gray-600 rounded-md bg-blue-50">Overview</a>
144
+ <a href="#" class="block px-3 py-2 text-gray-600 hover:bg-gray-50 rounded-md">Analytics</a>
145
+ <a href="#" class="block px-3 py-2 text-gray-600 hover:bg-gray-50 rounded-md">Users</a>
146
+ <a href="#" class="block px-3 py-2 text-gray-600 hover:bg-gray-50 rounded-md">Settings</a>
147
+ </nav>
148
+ </div>
149
+
150
+ <!-- Main Content -->
151
+ <div class="flex-1">
152
+ <header class="bg-white shadow-sm border-b px-6 py-4">
153
+ <h1 class="text-2xl font-semibold text-gray-900">Overview</h1>
154
+ </header>
155
+
156
+ <main class="p-6">
157
+ <div class="grid grid-cols-1 md:grid-cols-4 gap-6 mb-8">
158
+ <div class="bg-white p-6 rounded-lg shadow-sm">
159
+ <h3 class="text-sm font-medium text-gray-500">Total Users</h3>
160
+ <p class="text-2xl font-semibold text-gray-900">1,234</p>
161
+ </div>
162
+ <div class="bg-white p-6 rounded-lg shadow-sm">
163
+ <h3 class="text-sm font-medium text-gray-500">Revenue</h3>
164
+ <p class="text-2xl font-semibold text-gray-900">$12,345</p>
165
+ </div>
166
+ <div class="bg-white p-6 rounded-lg shadow-sm">
167
+ <h3 class="text-sm font-medium text-gray-500">Growth</h3>
168
+ <p class="text-2xl font-semibold text-gray-900">+23%</p>
169
+ </div>
170
+ <div class="bg-white p-6 rounded-lg shadow-sm">
171
+ <h3 class="text-sm font-medium text-gray-500">Churn</h3>
172
+ <p class="text-2xl font-semibold text-gray-900">-2.1%</p>
173
+ </div>
174
+ </div>
175
+
176
+ <div class="bg-white rounded-lg shadow-sm p-6">
177
+ <h3 class="text-lg font-medium text-gray-900 mb-4">Revenue Chart</h3>
178
+ <canvas id="revenueChart" width="400" height="200"></canvas>
179
+ </div>
180
+ </main>
181
+ </div>
182
+ </div>
183
+ </body>
184
+ </html>`,
185
+ js: `// SaaS Dashboard JavaScript
186
+ document.addEventListener('DOMContentLoaded', function() {
187
+ // Initialize chart
188
+ const ctx = document.getElementById('revenueChart').getContext('2d');
189
+ new Chart(ctx, {
190
+ type: 'line',
191
+ data: {
192
+ labels: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun'],
193
+ datasets: [{
194
+ label: 'Revenue',
195
+ data: [12000, 19000, 15000, 25000, 22000, 30000],
196
+ borderColor: 'rgb(59, 130, 246)',
197
+ backgroundColor: 'rgba(59, 130, 246, 0.1)',
198
+ tension: 0.4
199
+ }]
200
+ },
201
+ options: {
202
+ responsive: true,
203
+ scales: {
204
+ y: {
205
+ beginAtZero: true
206
+ }
207
+ }
208
+ }
209
+ });
210
+ });`
211
+ }
212
+ }
213
+ };
214
+
215
+ // Elements
216
+ const els = {
217
+ mobileMenuBtn: document.getElementById('mobile-menu-btn'),
218
+ chatList: document.getElementById('chat-list'),
219
+ newChatBtn: document.getElementById('new-chat'),
220
+ exportChatBtn: document.getElementById('export-chat'),
221
+ togglePreviewBtn: document.getElementById('toggle-preview'),
222
+ messages: document.getElementById('messages'),
223
+ welcome: document.getElementById('welcome'),
224
+ prompt: document.getElementById('prompt'),
225
+ sendBtn: document.getElementById('send-btn'),
226
+ stopBtn: document.getElementById('stop-btn'),
227
+ hfToken: document.getElementById('hf-token'),
228
+ modelSelect: document.getElementById('model-select'),
229
+ templateSelect: document.getElementById('template-select'),
230
+ statusText: document.getElementById('status-text'),
231
+ modelStatus: document.getElementById('model-status'),
232
+ previewPanel: document.getElementById('preview-panel'),
233
+ previewFrame: document.getElementById('preview-frame'),
234
+ refreshPreview: document.getElementById('refresh-preview'),
235
+ openPreview: document.getElementById('open-preview'),
236
+ closePreview: document.getElementById('close-preview'),
237
+ };
238
+
239
+ // Init
240
+ init();
241
+
242
+ function init() {
243
+ // Load saved settings
244
+ const savedToken = localStorage.getItem('hf_token') || '';
245
+ els.hfToken.value = savedToken;
246
+
247
+ // Events
248
+ els.newChatBtn.addEventListener('click', newChat);
249
+ els.exportChatBtn.addEventListener('click', exportCurrentChat);
250
+ els.togglePreviewBtn.addEventListener('click', togglePreview);
251
+ els.sendBtn.addEventListener('click', onSend);
252
+ els.stopBtn.addEventListener('click', stopGeneration);
253
+ els.prompt.addEventListener('keydown', onPromptKeyDown);
254
+ els.hfToken.addEventListener('change', () => {
255
+ localStorage.setItem('hf_token', els.hfToken.value.trim());
256
+ toast('Hugging Face token saved.');
257
+ });
258
+ els.modelSelect.addEventListener('change', () => {
259
+ localStorage.setItem('selected_model', els.modelSelect.value);
260
+ updateModelStatus('Model changed. Will load on first use.');
261
+ handleModelSelectionChange();
262
+ });
263
+ els.templateSelect.addEventListener('change', () => {
264
+ localStorage.setItem('selected_template', els.templateSelect.value);
265
+ });
266
+
267
+ // Local model upload events
268
+ els.uploadModelBtn.addEventListener('click', () => {
269
+ els.modelFileInput.click();
270
+ });
271
+
272
+ els.modelFileInput.addEventListener('change', handleModelFileSelect);
273
+ // Template selection events
274
+ els.templateSelect.addEventListener('change', () => {
275
+ localStorage.setItem('selected_template', els.templateSelect.value);
276
+ });
277
+
278
+ // Enhanced button interactions
279
+ addButtonInteractions();
280
+
281
+ // Preview events
282
+ els.refreshPreview.addEventListener('click', refreshPreview);
283
+ els.openPreview.addEventListener('click', openPreviewInWindow);
284
+ els.closePreview.addEventListener('click', togglePreview);
285
+ // Restore settings
286
+ const savedModel = localStorage.getItem('selected_model');
287
+ if (savedModel) els.modelSelect.value = savedModel;
288
+
289
+ const savedLocalModel = localStorage.getItem('selected_local_model');
290
+ if (savedLocalModel) {
291
+ selectedLocalModel = JSON.parse(savedLocalModel);
292
+ updateLocalModelDisplay();
293
+ }
294
+
295
+ const savedTemplate = localStorage.getItem('selected_template');
296
+ if (savedTemplate) els.templateSelect.value = savedTemplate;
297
+ // Render chat list
298
+ renderChatList();
299
+
300
+ // Auto-select last chat or create new
301
+ if (chats.length > 0) {
302
+ openChat(chats[chats.length - 1].id);
303
+ } else {
304
+ newChat();
305
+ }
306
+
307
+ // Auto-resize textarea
308
+ autoResize(els.prompt);
309
+ els.prompt.addEventListener('input', () => autoResize(els.prompt));
310
+ }
311
+
312
+ function addButtonInteractions() {
313
+ // Add ripple effect to all buttons
314
+ document.querySelectorAll('button').forEach(button => {
315
+ button.addEventListener('click', function(e) {
316
+ const ripple = document.createElement('span');
317
+ const rect = this.getBoundingClientRect();
318
+ const size = Math.max(rect.width, rect.height);
319
+ const x = e.clientX - rect.left - size / 2;
320
+ const y = e.clientY - rect.top - size / 2;
321
+
322
+ ripple.style.width = ripple.style.height = size + 'px';
323
+ ripple.style.left = x + 'px';
324
+ ripple.style.top = y + 'px';
325
+ ripple.classList.add('ripple');
326
+
327
+ this.appendChild(ripple);
328
+
329
+ setTimeout(() => {
330
+ ripple.remove();
331
+ }, 600);
332
+ });
333
+ });
334
+ }
335
+
336
+ // Add CSS for ripple effect
337
+ const style = document.createElement('style');
338
+ style.textContent = `
339
+ .ripple {
340
+ position: absolute;
341
+ border-radius: 50%;
342
+ background: rgba(255, 255, 255, 0.3);
343
+ transform: scale(0);
344
+ animation: ripple-animation 0.6s linear;
345
+ pointer-events: none;
346
+ }
347
+
348
+ @keyframes ripple-animation {
349
+ to {
350
+ transform: scale(4);
351
+ opacity: 0;
352
+ }
353
+ }
354
+ `;
355
+ document.head.appendChild(style);
356
+ function loadChats() {
357
+ try {
358
+ return JSON.parse(localStorage.getItem('deepsite_chats') || '[]');
359
+ } catch {
360
+ return [];
361
+ }
362
+ }
363
+ function saveChats() {
364
+ localStorage.setItem('deepsite_chats', JSON.stringify(chats));
365
+ }
366
+
367
+ function renderChatList() {
368
+ els.chatList.innerHTML = '';
369
+ if (chats.length === 0) {
370
+ els.chatList.innerHTML = '<li class="text-sm text-gray-500 px-3 py-2">No chats yet</li>';
371
+ return;
372
+ }
373
+ chats.forEach(c => {
374
+ const li = document.createElement('li');
375
+ li.className = 'group';
376
+ li.innerHTML = `
377
+ <button class="w-full text-left px-3 py-2 rounded-md hover:bg-gray-100 ${currentChatId === c.id ? 'bg-gray-100' : ''}">
378
+ <div class="flex items-start justify-between">
379
+ <div class="truncate text-sm">
380
+ ${escapeHTML(c.title || 'Untitled')}
381
+ </div>
382
+ <div class="flex items-center gap-1 opacity-0 group-hover:opacity-100 transition">
383
+ <button data-action="rename" class="p-1 hover:bg-gray-200 rounded" title="Rename">
384
+ <svg xmlns="http://www.w3.org/2000/svg" class="w-4 h-4 text-gray-500" viewBox="0 0 24 24" fill="none" stroke="currentColor">
385
+ <path stroke-width="2" d="M4 21h4l10.5-10.5a1.5 1.5 0 000-2.12l-2.88-2.88a1.5 1.5 0 00-2.12 0L3 16v5z"/>
386
+ </svg>
387
+ </button>
388
+ <button data-action="delete" class="p-1 hover:bg-gray-200 rounded" title="Delete">
389
+ <svg xmlns="http://www.w3.org/2000/svg" class="w-4 h-4 text-red-600" viewBox="0 0 24 24" fill="none" stroke="currentColor">
390
+ <path stroke-width="2" d="M3 6h18M8 6V4h8v2M6 6l1 14h10l1-14"/>
391
+ </svg>
392
+ </button>
393
+ </div>
394
+ </div>
395
+ <div class="text-[11px] text-gray-500">${formatDate(c.createdAt)}</div>
396
+ </button>
397
+ `;
398
+ li.addEventListener('click', (e) => {
399
+ const actBtn = e.target.closest('button[data-action]');
400
+ if (actBtn) {
401
+ e.stopPropagation();
402
+ const action = actBtn.getAttribute('data-action');
403
+ if (action === 'rename') renameChat(c.id);
404
+ if (action === 'delete') deleteChat(c.id);
405
+ return;
406
+ }
407
+ openChat(c.id);
408
+ });
409
+ els.chatList.appendChild(li);
410
+ });
411
+ }
412
+
413
+ function newChat() {
414
+ const id = uid();
415
+ const chat = { id, title: 'New Chat', createdAt: Date.now(), messages: [] };
416
+ chats.push(chat);
417
+ saveChats();
418
+ renderChatList();
419
+ openChat(id);
420
+ }
421
+
422
+ function openChat(id) {
423
+ currentChatId = id;
424
+ const chat = chats.find(c => c.id === id);
425
+ if (!chat) return;
426
+ renderMessages(chat);
427
+ renderChatList();
428
+ els.prompt.focus();
429
+ }
430
+
431
+ function deleteChat(id) {
432
+ const idx = chats.findIndex(c => c.id === id);
433
+ if (idx === -1) return;
434
+ chats.splice(idx, 1);
435
+ saveChats();
436
+ renderChatList();
437
+ if (currentChatId === id) {
438
+ if (chats.length > 0) openChat(chats[chats.length - 1].id);
439
+ else newChat();
440
+ }
441
+ }
442
+
443
+ function renameChat(id) {
444
+ const chat = chats.find(c => c.id === id);
445
+ if (!chat) return;
446
+ const title = prompt('Rename chat:', chat.title);
447
+ if (title && title.trim()) {
448
+ chat.title = title.trim();
449
+ saveChats();
450
+ renderChatList();
451
+ }
452
+ }
453
+
454
+ function renderMessages(chat) {
455
+ els.messages.innerHTML = '';
456
+ if (!chat.messages || chat.messages.length === 0) {
457
+ els.messages.appendChild(els.welcome);
458
+ els.welcome.classList.remove('hidden');
459
+ } else {
460
+ els.welcome.classList.add('hidden');
461
+ chat.messages.forEach(m => {
462
+ const el = renderMessage(m);
463
+ els.messages.appendChild(el);
464
+ });
465
+ scrollToBottom();
466
+ }
467
+ }
468
+
469
+ function renderMessage(message) {
470
+ const wrap = document.createElement('div');
471
+ wrap.className = `message ${message.role}`;
472
+ const avatar = document.createElement('div');
473
+ avatar.className = 'avatar';
474
+ avatar.textContent = message.role === 'user' ? 'U' : 'AI';
475
+ const bubble = document.createElement('div');
476
+ bubble.className = 'bubble';
477
+ bubble.innerHTML = escapeHTML(message.content || '');
478
+ wrap.appendChild(avatar);
479
+ wrap.appendChild(bubble);
480
+ return wrap;
481
+ }
482
+ async function onSend() {
483
+ const text = els.prompt.value.trim();
484
+ if (!text) return;
485
+
486
+ // Ensure chat exists
487
+ if (!currentChatId) newChat();
488
+ const chat = chats.find(c => c.id === currentChatId);
489
+
490
+ // Show status
491
+ setStatus('Thinking...');
492
+
493
+ // Append user message
494
+ const userMsg = { role: 'user', content: text };
495
+ chat.messages.push(userMsg);
496
+ const userEl = renderMessage(userMsg);
497
+ els.messages.appendChild(userEl);
498
+ els.welcome.classList.add('hidden');
499
+ scrollToBottom();
500
+
501
+ // Clear prompt
502
+ els.prompt.value = '';
503
+ autoResize(els.prompt);
504
+
505
+ // Prepare assistant message placeholder
506
+ const assistantMsg = { role: 'assistant', content: '', type: 'code' };
507
+ chat.messages.push(assistantMsg);
508
+ const assistantEl = renderMessage(assistantMsg);
509
+ assistantEl.querySelector('.bubble').innerHTML = '';
510
+ els.messages.appendChild(assistantEl);
511
+
512
+ // UI state
513
+ els.sendBtn.disabled = true;
514
+ els.stopBtn.classList.remove('hidden');
515
+ setStatus('Generating code...');
516
+
517
+ // Create enhanced prompt for code generation
518
+ const template = els.templateSelect.value;
519
+ const enhancedPrompt = createEnhancedPrompt(text, template);
520
+
521
+ try {
522
+ // Simulate AI response with template-based generation
523
+ const generatedCode = await generateCode(enhancedPrompt, template);
524
+
525
+ if (generatedCode) {
526
+ assistantMsg.content = generatedCode;
527
+ const bubble = assistantEl.querySelector('.bubble');
528
+ bubble.innerHTML = formatCodeDisplay(generatedCode);
529
+
530
+ // Auto-update preview if visible
531
+ if (previewVisible) {
532
+ updatePreview(generatedCode);
533
+ }
534
+
535
+ // Add "Run Preview" button
536
+ addPreviewButton(assistantEl, generatedCode);
537
+ }
538
+ } catch (error) {
539
+ console.error('Generation error:', error);
540
+ toast('Generation failed. Please try again.');
541
+
542
+ const bubble = assistantEl.querySelector('.bubble');
543
+ if (bubble && !bubble.textContent.trim()) {
544
+ bubble.textContent = 'Sorry, something went wrong while generating the code.';
545
+ }
546
+ } finally {
547
+ setStatus('Ready');
548
+ els.sendBtn.disabled = false;
549
+ els.stopBtn.classList.add('hidden');
550
+ saveChats();
551
+ }
552
+ }
553
+
554
+ function createEnhancedPrompt(userInput, template) {
555
+ const systemPrompts = {
556
+ vibecode: `You are an expert full-stack developer specializing in modern, trendy web applications with vibecoding aesthetics. Create clean, responsive code using:
557
+ - Tailwind CSS for styling
558
+ - Modern JavaScript (ES6+)
559
+ - Gradient backgrounds and glassmorphism effects
560
+ - Smooth animations and transitions
561
+ - Mobile-first responsive design
562
+
563
+ Generate complete, functional HTML code for: ${userInput}`,
564
+ website: `You are a professional web developer creating clean, modern websites. Focus on:
565
+ - Semantic HTML structure
566
+ - Tailwind CSS for professional styling
567
+ - Responsive design principles
568
+ - Accessibility best practices
569
+ - Clean, maintainable code
570
+
571
+ Generate complete HTML for: ${userInput}`,
572
+ saas: `You are a SaaS developer creating admin dashboards and business applications. Create:
573
+ - Professional dashboard layouts
574
+ - Data visualization components
575
+ - User management interfaces
576
+ - Analytics and reporting features
577
+ - Modern UI/UX patterns
578
+
579
+ Generate complete HTML for: ${userInput}`,
580
+ landing: `You are a landing page specialist creating conversion-optimized pages. Focus on:
581
+ - Hero sections with compelling CTAs
582
+ - Feature showcases
583
+ - Social proof sections
584
+ - Contact/lead capture forms
585
+ - Mobile-optimized designs
586
+
587
+ Generate complete HTML for: ${userInput}`,
588
+ portfolio: `You are a portfolio designer creating impressive personal websites. Create:
589
+ - Creative layouts showcasing projects
590
+ - About sections with personality
591
+ - Contact forms and social links
592
+ - Interactive elements
593
+ - Performance-optimized code
594
+
595
+ Generate complete HTML for: ${userInput}`,
596
+ ecommerce: `You are an e-commerce developer creating online store interfaces. Build:
597
+ - Product catalog layouts
598
+ - Shopping cart functionality
599
+ - Checkout processes
600
+ - Search and filtering
601
+ - Modern e-commerce UX
602
+
603
+ Generate complete HTML for: ${userInput}`,
604
+ blog: `You are a blog/CMS developer creating content-focused websites. Create:
605
+ - Clean reading layouts
606
+ - Article metadata
607
+ - Comment systems
608
+ - Author profiles
609
+ - Content organization
610
+
611
+ Generate complete HTML for: ${userInput}`
612
+ };
613
+
614
+ return systemPrompts[template] || systemPrompts.vibecode;
615
+ }
616
+
617
+ async function generateCode(prompt, template) {
618
+ // For demo purposes, return template-based generated code
619
+ // In a real implementation, this would call the local model
620
+
621
+ // Simulate processing time
622
+ await new Promise(resolve => setTimeout(resolve, 1500));
623
+
624
+ const templateStruct = templates[template] || templates.vibecode;
625
+
626
+ // Enhance the template based on user input
627
+ let generatedHTML = templateStruct.structure.html;
628
+ let generatedJS = templateStruct.structure.js;
629
+
630
+ // Simple keyword replacement based on user input
631
+ const keywords = prompt.toLowerCase();
632
+
633
+ if (keywords.includes('dark') || keywords.includes('night')) {
634
+ generatedHTML = generatedHTML.replace('bg-white', 'bg-gray-900')
635
+ .replace('text-gray-900', 'text-white')
636
+ .replace('text-gray-600', 'text-gray-300');
637
+ }
638
+
639
+ if (keywords.includes('colorful') || keywords.includes('rainbow')) {
640
+ generatedHTML = generatedHTML.replace(
641
+ 'background: linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
642
+ 'background: linear-gradient(135deg, #ff6b6b, #4ecdc4, #45b7d1, #96ceb4, #feca57)'
643
+ );
644
+ }
645
+
646
+ if (keywords.includes('dashboard') || keywords.includes('analytics')) {
647
+ generatedHTML = generatedHTML.replace('VibeApp', 'DataViz Dashboard')
648
+ .replace('Modern vibes, clean code, smooth UX', 'Real-time insights and analytics');
649
+ }
650
+
651
+ return {
652
+ html: generatedHTML,
653
+ js: generatedJS,
654
+ preview: generatedHTML.replace('</body>', `<script>${generatedJS}</script></body>`)
655
+ };
656
+ }
657
+
658
+ function formatCodeDisplay(codeObj) {
659
+ return `
660
+ <div class="code-generation">
661
+ <div class="mb-4">
662
+ <h4 class="font-semibold text-sm mb-2 flex items-center gap-2">
663
+ <span class="w-2 h-2 bg-green-500 rounded-full"></span>
664
+ Generated HTML
665
+ </h4>
666
+ <div class="bg-gray-50 p-3 rounded border text-xs overflow-x-auto">
667
+ <pre><code>${escapeHTML(codeObj.html)}</code></pre>
668
+ </div>
669
+ </div>
670
+
671
+ <div class="mb-4">
672
+ <h4 class="font-semibold text-sm mb-2 flex items-center gap-2">
673
+ <span class="w-2 h-2 bg-blue-500 rounded-full"></span>
674
+ JavaScript
675
+ </h4>
676
+ <div class="bg-gray-50 p-3 rounded border text-xs overflow-x-auto">
677
+ <pre><code>${escapeHTML(codeObj.js)}</code></pre>
678
+ </div>
679
+ </div>
680
+
681
+ <button onclick="runPreview('${escapeHTML(codeObj.preview)}')" class="w-full bg-neon-600 text-white px-4 py-2 rounded hover:bg-neon-700 transition">
682
+ πŸš€ Run Live Preview
683
+ </button>
684
+ </div>
685
+ `;
686
+ }
687
+
688
+ function addPreviewButton(assistantEl, codeObj) {
689
+ // Already handled in formatCodeDisplay
690
+ }
691
+
692
+ function runPreview(previewHTML) {
693
+ generatedHTML = previewHTML;
694
+ if (!previewVisible) {
695
+ togglePreview();
696
+ }
697
+ updatePreview(previewHTML);
698
+ }
699
+
700
+ function togglePreview() {
701
+ previewVisible = !previewVisible;
702
+ if (previewVisible) {
703
+ els.previewPanel.classList.remove('hidden');
704
+ els.togglePreviewBtn.textContent = 'Hide Preview';
705
+ if (generatedHTML) {
706
+ updatePreview(generatedHTML);
707
+ }
708
+ } else {
709
+ els.previewPanel.classList.add('hidden');
710
+ els.togglePreviewBtn.textContent = 'Preview';
711
+ }
712
+ }
713
+
714
+ function updatePreview(htmlContent) {
715
+ // Create a complete HTML document if needed
716
+ if (!htmlContent.includes('<!DOCTYPE') && !htmlContent.includes('<html')) {
717
+ htmlContent = `<!DOCTYPE html><html><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Generated Code</title></head>${htmlContent}</html>`;
718
+ }
719
+
720
+ // Update iframe
721
+ els.previewFrame.srcdoc = htmlContent;
722
+ toast('Preview updated!');
723
+ }
724
+
725
+ function refreshPreview() {
726
+ if (generatedHTML) {
727
+ updatePreview(generatedHTML);
728
+ toast('Preview refreshed!');
729
+ }
730
+ }
731
+
732
+ function openPreviewInWindow() {
733
+ if (generatedHTML) {
734
+ const newWindow = window.open('', '_blank');
735
+ newWindow.document.write(generatedHTML);
736
+ newWindow.document.close();
737
+ }
738
+ }
739
+
740
+ function stopGeneration() {
741
+ abortController = null;
742
+ els.stopBtn.classList.add('hidden');
743
+ els.sendBtn.disabled = false;
744
+ setStatus('Stopped');
745
+ }
746
+ function onPromptKeyDown(e) {
747
+ if (e.key === 'Enter' && !e.shiftKey) {
748
+ e.preventDefault();
749
+ onSend();
750
+ }
751
+ }
752
+
753
+ function exportCurrentChat() {
754
+ const chat = chats.find(c => c.id === currentChatId);
755
+ if (!chat) return;
756
+ const data = {
757
+ title: chat.title,
758
+ model: els.modelSelect.value,
759
+ createdAt: chat.createdAt,
760
+ messages: chat.messages
761
+ };
762
+ const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' });
763
+ const url = URL.createObjectURL(blob);
764
+ const a = document.createElement('a');
765
+ a.href = url;
766
+ a.download = `${sanitizeFilename(chat.title || 'chat')}.json`;
767
+ document.body.appendChild(a);
768
+ a.click();
769
+ a.remove();
770
+ URL.revokeObjectURL(url);
771
+ }
772
+
773
+ function scrollToBottom() {
774
+ els.messages.scrollTo({ top: els.messages.scrollHeight, behavior: 'smooth' });
775
+ }
776
+
777
+ function autoResize(el) {
778
+ el.style.height = 'auto';
779
+ el.style.height = Math.min(el.scrollHeight, 200) + 'px';
780
+ }
781
+
782
+ function setStatus(text) {
783
+ els.statusText.textContent = text;
784
+ }
785
+ function toast(msg) {
786
+ // Simple non-blocking toast
787
+ const t = document.createElement('div');
788
+ t.textContent = msg;
789
+ t.className = 'fixed bottom-4 left-1/2 -translate-x-1/2 px-4 py-2 bg-gray-900 text-white text-sm rounded-md shadow-lg z-[60]';
790
+ document.body.appendChild(t);
791
+ setTimeout(() => t.remove(), 2200);
792
+ }
793
+
794
+ function updateModelStatus(msg) {
795
+ els.modelStatus.textContent = msg ? `β€’ ${msg}` : '';
796
+ }
797
+
798
+ // Quick template functions
799
+ window.quickTemplate = function(template) {
800
+ els.templateSelect.value = template;
801
+ const templateInfo = templates[template];
802
+ if (templateInfo) {
803
+ els.prompt.value = `Create a ${templateInfo.description}`;
804
+ autoResize(els.prompt);
805
+ }
806
+ };
807
+
808
+ window.runPreview = runPreview;
809
+
810
+ function uid() {
811
+ return Math.random().toString(36).slice(2) + Date.now().toString(36);
812
+ }
813
+ function escapeHTML(str) {
814
+ return String(str).replace(/[&<>"']/g, s => ({
815
+ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;'
816
+ }[s]));
817
+ }
818
+
819
+ function formatDate(ts) {
820
+ try {
821
+ return new Date(ts).toLocaleString();
822
+ } catch {
823
+ return '';
824
+ }
825
+ }
826
+
827
+ function sanitizeFilename(name) {
828
+ return String(name).replace(/[^\w\-]+/g, '_').slice(0, 64);
829
+ }
830
+
831
+ async
832
+ function safeText(res) {
833
+ try { return await res.text(); } catch { return ''; }
834
+ }
835
+ })();
server.js ADDED
@@ -0,0 +1,508 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ const express = require('express');
3
+ const path = require('path');
4
+ const cors = require('cors');
5
+
6
+ const app = express();
7
+ const PORT = process.env.PORT || 3000;
8
+
9
+ // Simple in-memory rate limit per IP (token bucket)
10
+ const buckets = new Map();
11
+ function rateLimit(req, res, next) {
12
+ const ip = req.ip || req.connection.remoteAddress || 'unknown';
13
+ const now = Date.now();
14
+ const cap = 100; // tokens
15
+ const refill = 50; // tokens per minute
16
+ const b = buckets.get(ip) || { tokens: cap, last: now };
17
+ const elapsed = (now - b.last) / 1000 / 60; // minutes
18
+ b.tokens = Math.min(cap, b.tokens + elapsed * refill);
19
+ b.last = now;
20
+ if (b.tokens < 1) {
21
+ return res.status(429).json({ error: 'Too many requests. Please slow down.' });
22
+ }
23
+ b.tokens -= 1;
24
+ buckets.set(ip, b);
25
+ next();
26
+ }
27
+
28
+ // Middleware
29
+ app.use(cors());
30
+ app.use(express.json({ limit: '2mb' }));
31
+
32
+ // Health check
33
+ app.get('/api/health', (req, res) => {
34
+ res.json({
35
+ ok: true,
36
+ time: Date.now(),
37
+ service: 'VibeCode Local AI',
38
+ version: '2.0.0'
39
+ });
40
+ });
41
+
42
+ // Template generation endpoint
43
+ app.post('/api/generate', rateLimit, async (req, res) => {
44
+ try {
45
+ const { prompt, template, style } = req.body || {};
46
+
47
+ if (!prompt || !template) {
48
+ return res.status(400).json({
49
+ error: 'Missing required parameters: prompt, template'
50
+ });
51
+ }
52
+
53
+ // Template configurations
54
+ const templates = {
55
+ vibecode: {
56
+ name: "VibeCode App",
57
+ description: "Modern web app with vibecoding aesthetics",
58
+ structure: {
59
+ html: `<!DOCTYPE html>
60
+ <html lang="en">
61
+ <head>
62
+ <meta charset="UTF-8">
63
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
64
+ <title>VibeApp</title>
65
+ <script src="https://cdn.tailwindcss.com"></script>
66
+ <style>
67
+ .gradient-bg {
68
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
69
+ }
70
+ .glass {
71
+ backdrop-filter: blur(16px) saturate(180%);
72
+ background-color: rgba(255, 255, 255, 0.75);
73
+ border: 1px solid rgba(255, 255, 255, 0.125);
74
+ }
75
+ .neon {
76
+ text-shadow: 0 0 10px currentColor;
77
+ }
78
+ @keyframes float {
79
+ 0%, 100% { transform: translateY(0px); }
80
+ 50% { transform: translateY(-10px); }
81
+ }
82
+ .float { animation: float 6s ease-in-out infinite; }
83
+ </style>
84
+ </head>
85
+ <body class="gradient-bg min-h-screen">
86
+ <div class="container mx-auto px-4 py-8">
87
+ <header class="text-center mb-12">
88
+ <h1 class="text-4xl font-bold text-white mb-4 neon float">${prompt}</h1>
89
+ <p class="text-white/80">Modern vibes, clean code, smooth UX</p>
90
+ </header>
91
+
92
+ <div class="grid md:grid-cols-3 gap-6">
93
+ <div class="glass rounded-lg p-6 shadow-lg hover:shadow-xl transition">
94
+ <h3 class="text-lg font-semibold mb-3">Dashboard</h3>
95
+ <p class="text-sm opacity-80">Clean overview of your data</p>
96
+ </div>
97
+ <div class="glass rounded-lg p-6 shadow-lg hover:shadow-xl transition">
98
+ <h3 class="text-lg font-semibold mb-3">Analytics</h3>
99
+ <p class="text-sm opacity-80">Real-time insights</p>
100
+ </div>
101
+ <div class="glass rounded-lg p-6 shadow-lg hover:shadow-xl transition">
102
+ <h3 class="text-lg font-semibold mb-3">Settings</h3>
103
+ <p class="text-sm opacity-80">Configure your vibe</p>
104
+ </div>
105
+ </div>
106
+ </div>
107
+ </body>
108
+ </html>`,
109
+ js: `// VibeApp JavaScript
110
+ console.log('${prompt} loaded!');
111
+ document.addEventListener('DOMContentLoaded', function() {
112
+ // Add smooth animations
113
+ const cards = document.querySelectorAll('.glass');
114
+ cards.forEach((card, index) => {
115
+ card.style.opacity = '0';
116
+ card.style.transform = 'translateY(20px)';
117
+ setTimeout(() => {
118
+ card.style.transition = 'all 0.5s ease';
119
+ card.style.opacity = '1';
120
+ card.style.transform = 'translateY(0)';
121
+ }, index * 100);
122
+ });
123
+
124
+ // Add hover effects
125
+ const hero = document.querySelector('.neon');
126
+ if (hero) {
127
+ hero.addEventListener('mouseenter', function() {
128
+ this.style.textShadow = '0 0 20px currentColor, 0 0 30px currentColor';
129
+ });
130
+ hero.addEventListener('mouseleave', function() {
131
+ this.style.textShadow = '0 0 10px currentColor';
132
+ });
133
+ }
134
+ });`
135
+ }
136
+ },
137
+ website: {
138
+ name: "Clean Website",
139
+ description: "Professional business website",
140
+ structure: {
141
+ html: `<!DOCTYPE html>
142
+ <html lang="en">
143
+ <head>
144
+ <meta charset="UTF-8">
145
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
146
+ <title>Professional Website - ${prompt}</title>
147
+ <script src="https://cdn.tailwindcss.com"></script>
148
+ </head>
149
+ <body class="bg-white">
150
+ <nav class="bg-white shadow-sm border-b">
151
+ <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
152
+ <div class="flex justify-between h-16">
153
+ <div class="flex items-center">
154
+ <h1 class="text-xl font-bold text-gray-900">YourBrand</h1>
155
+ </div>
156
+ <div class="hidden md:flex items-center space-x-8">
157
+ <a href="#" class="text-gray-600 hover:text-gray-900 transition">Home</a>
158
+ <a href="#" class="text-gray-600 hover:text-gray-900 transition">About</a>
159
+ <a href="#" class="text-gray-600 hover:text-gray-900 transition">Services</a>
160
+ <a href="#" class="text-gray-600 hover:text-gray-900 transition">Contact</a>
161
+ </div>
162
+ </div>
163
+ </div>
164
+ </nav>
165
+
166
+ <main>
167
+ <section class="bg-gradient-to-r from-blue-600 to-purple-600 text-white">
168
+ <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-24">
169
+ <div class="text-center">
170
+ <h1 class="text-5xl font-bold mb-6">${prompt}</h1>
171
+ <p class="text-xl mb-8">We create amazing digital experiences</p>
172
+ <button class="bg-white text-blue-600 px-8 py-3 rounded-lg font-semibold hover:bg-gray-100 transition">
173
+ Get Started
174
+ </button>
175
+ </div>
176
+ </div>
177
+ </section>
178
+
179
+ <section class="py-16">
180
+ <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
181
+ <div class="grid md:grid-cols-3 gap-8">
182
+ <div class="text-center">
183
+ <div class="w-16 h-16 bg-blue-100 rounded-full flex items-center justify-center mx-auto mb-4">
184
+ <svg class="w-8 h-8 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
185
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z"></path>
186
+ </svg>
187
+ </div>
188
+ <h3 class="text-lg font-semibold mb-2">Fast & Efficient</h3>
189
+ <p class="text-gray-600">Lightning-fast performance and optimized workflows</p>
190
+ </div>
191
+ <div class="text-center">
192
+ <div class="w-16 h-16 bg-purple-100 rounded-full flex items-center justify-center mx-auto mb-4">
193
+ <svg class="w-8 h-8 text-purple-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
194
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"></path>
195
+ </svg>
196
+ </div>
197
+ <h3 class="text-lg font-semibold mb-2">Quality Assured</h3>
198
+ <p class="text-gray-600">Every project meets our highest standards</p>
199
+ </div>
200
+ <div class="text-center">
201
+ <div class="w-16 h-16 bg-green-100 rounded-full flex items-center justify-center mx-auto mb-4">
202
+ <svg class="w-8 h-8 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
203
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M18.364 5.636l-3.536 3.536m0 5.656l3.536 3.536M9.172 9.172L5.636 5.636m3.536 9.192L5.636 18.364M12 2.25a9.75 9.75 0 109.75 9.75A9.75 9.75 0 0012 2.25z"></path>
204
+ </svg>
205
+ </div>
206
+ <h3 class="text-lg font-semibold mb-2">24/7 Support</h3>
207
+ <p class="text-gray-600">Round-the-clock assistance when you need it</p>
208
+ </div>
209
+ </div>
210
+ </div>
211
+ </section>
212
+ </main>
213
+ </body>
214
+ </html>`,
215
+ js: `// Professional Website JavaScript
216
+ document.addEventListener('DOMContentLoaded', function() {
217
+ console.log('Professional website loaded successfully');
218
+
219
+ // Smooth scrolling for anchor links
220
+ document.querySelectorAll('a[href^="#"]').forEach(anchor => {
221
+ anchor.addEventListener('click', function (e) {
222
+ e.preventDefault();
223
+ document.querySelector(this.getAttribute('href')).scrollIntoView({
224
+ behavior: 'smooth'
225
+ });
226
+ });
227
+ });
228
+
229
+ // Add intersection observer for animations
230
+ const observerOptions = {
231
+ threshold: 0.1,
232
+ rootMargin: '0px 0px -50px 0px'
233
+ };
234
+
235
+ const observer = new IntersectionObserver(function(entries) {
236
+ entries.forEach(entry => {
237
+ if (entry.isIntersecting) {
238
+ entry.target.style.opacity = '1';
239
+ entry.target.style.transform = 'translateY(0)';
240
+ }
241
+ });
242
+ }, observerOptions);
243
+
244
+ // Observe elements for animation
245
+ document.querySelectorAll('.text-center').forEach(el => {
246
+ el.style.opacity = '0';
247
+ el.style.transform = 'translateY(20px)';
248
+ el.style.transition = 'all 0.6s ease';
249
+ observer.observe(el);
250
+ });
251
+ });`
252
+ }
253
+ },
254
+ saas: {
255
+ name: "SaaS Dashboard",
256
+ description: "Analytics & admin panel",
257
+ structure: {
258
+ html: `<!DOCTYPE html>
259
+ <html lang="en">
260
+ <head>
261
+ <meta charset="UTF-8">
262
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
263
+ <title>SaaS Dashboard - ${prompt}</title>
264
+ <script src="https://cdn.tailwindcss.com"></script>
265
+ <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
266
+ </head>
267
+ <body class="bg-gray-50">
268
+ <div class="min-h-screen flex">
269
+ <!-- Sidebar -->
270
+ <div class="w-64 bg-white shadow-sm">
271
+ <div class="p-6">
272
+ <h2 class="text-xl font-bold text-gray-800">Dashboard</h2>
273
+ </div>
274
+ <nav class="px-4 space-y-2">
275
+ <a href="#" class="block px-3 py-2 text-gray-600 rounded-md bg-blue-50 border-r-2 border-blue-500">Overview</a>
276
+ <a href="#" class="block px-3 py-2 text-gray-600 hover:bg-gray-50 rounded-md">Analytics</a>
277
+ <a href="#" class="block px-3 py-2 text-gray-600 hover:bg-gray-50 rounded-md">Users</a>
278
+ <a href="#" class="block px-3 py-2 text-gray-600 hover:bg-gray-50 rounded-md">Settings</a>
279
+ </nav>
280
+ </div>
281
+
282
+ <!-- Main Content -->
283
+ <div class="flex-1">
284
+ <header class="bg-white shadow-sm border-b px-6 py-4">
285
+ <h1 class="text-2xl font-semibold text-gray-900">${prompt}</h1>
286
+ <p class="text-gray-600">Welcome back! Here's what's happening with your business.</p>
287
+ </header>
288
+
289
+ <main class="p-6">
290
+ <div class="grid grid-cols-1 md:grid-cols-4 gap-6 mb-8">
291
+ <div class="bg-white p-6 rounded-lg shadow-sm hover:shadow-md transition">
292
+ <div class="flex items-center justify-between">
293
+ <div>
294
+ <h3 class="text-sm font-medium text-gray-500">Total Users</h3>
295
+ <p class="text-2xl font-semibold text-gray-900">1,234</p>
296
+ <span class="text-sm text-green-600">+12%</span>
297
+ </div>
298
+ <div class="w-12 h-12 bg-blue-100 rounded-full flex items-center justify-center">
299
+ <svg class="w-6 h-6 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
300
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197m13.5-9a2.5 2.5 0 11-5 0 2.5 2.5 0 015 0z"></path>
301
+ </svg>
302
+ </div>
303
+ </div>
304
+ </div>
305
+
306
+ <div class="bg-white p-6 rounded-lg shadow-sm hover:shadow-md transition">
307
+ <div class="flex items-center justify-between">
308
+ <div>
309
+ <h3 class="text-sm font-medium text-gray-500">Revenue</h3>
310
+ <p class="text-2xl font-semibold text-gray-900">$12,345</p>
311
+ <span class="text-sm text-green-600">+8.2%</span>
312
+ </div>
313
+ <div class="w-12 h-12 bg-green-100 rounded-full flex items-center justify-center">
314
+ <svg class="w-6 h-6 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
315
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8c-1.657 0-3 .895-3 2s1.343 2 3 2 3 .895 3 2-1.343 2-3 2m0-8c1.11 0 2.08.402 2.599 1M12 8V7m0 1v8m0 0v1m0-1c-1.11 0-2.08-.402-2.599-1"></path>
316
+ </svg>
317
+ </div>
318
+ </div>
319
+ </div>
320
+
321
+ <div class="bg-white p-6 rounded-lg shadow-sm hover:shadow-md transition">
322
+ <div class="flex items-center justify-between">
323
+ <div>
324
+ <h3 class="text-sm font-medium text-gray-500">Growth</h3>
325
+ <p class="text-2xl font-semibold text-gray-900">+23%</p>
326
+ <span class="text-sm text-green-600">+4.1%</span>
327
+ </div>
328
+ <div class="w-12 h-12 bg-purple-100 rounded-full flex items-center justify-center">
329
+ <svg class="w-6 h-6 text-purple-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
330
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 7h8m0 0v8m0-8l-8 8-4-4-6 6"></path>
331
+ </svg>
332
+ </div>
333
+ </div>
334
+ </div>
335
+
336
+ <div class="bg-white p-6 rounded-lg shadow-sm hover:shadow-md transition">
337
+ <div class="flex items-center justify-between">
338
+ <div>
339
+ <h3 class="text-sm font-medium text-gray-500">Active Users</h3>
340
+ <p class="text-2xl font-semibold text-gray-900">856</p>
341
+ <span class="text-sm text-red-600">-2.1%</span>
342
+ </div>
343
+ <div class="w-12 h-12 bg-yellow-100 rounded-full flex items-center justify-center">
344
+ <svg class="w-6 h-6 text-yellow-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
345
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"></path>
346
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"></path>
347
+ </svg>
348
+ </div>
349
+ </div>
350
+ </div>
351
+ </div>
352
+
353
+ <div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
354
+ <div class="bg-white rounded-lg shadow-sm p-6">
355
+ <h3 class="text-lg font-medium text-gray-900 mb-4">Revenue Chart</h3>
356
+ <canvas id="revenueChart" width="400" height="200"></canvas>
357
+ </div>
358
+
359
+ <div class="bg-white rounded-lg shadow-sm p-6">
360
+ <h3 class="text-lg font-medium text-gray-900 mb-4">User Activity</h3>
361
+ <canvas id="activityChart" width="400" height="200"></canvas>
362
+ </div>
363
+ </div>
364
+ </main>
365
+ </div>
366
+ </div>
367
+ </body>
368
+ </html>`,
369
+ js: `// SaaS Dashboard JavaScript
370
+ document.addEventListener('DOMContentLoaded', function() {
371
+ // Initialize revenue chart
372
+ const ctx1 = document.getElementById('revenueChart').getContext('2d');
373
+ new Chart(ctx1, {
374
+ type: 'line',
375
+ data: {
376
+ labels: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun'],
377
+ datasets: [{
378
+ label: 'Revenue',
379
+ data: [12000, 19000, 15000, 25000, 22000, 30000],
380
+ borderColor: 'rgb(59, 130, 246)',
381
+ backgroundColor: 'rgba(59, 130, 246, 0.1)',
382
+ tension: 0.4,
383
+ fill: true
384
+ }]
385
+ },
386
+ options: {
387
+ responsive: true,
388
+ plugins: {
389
+ legend: {
390
+ display: false
391
+ }
392
+ },
393
+ scales: {
394
+ y: {
395
+ beginAtZero: true,
396
+ ticks: {
397
+ callback: function(value) {
398
+ return ' + value.toLocaleString();
399
+ }
400
+ }
401
+ }
402
+ }
403
+ }
404
+ });
405
+
406
+ // Initialize activity chart
407
+ const ctx2 = document.getElementById('activityChart').getContext('2d');
408
+ new Chart(ctx2, {
409
+ type: 'doughnut',
410
+ data: {
411
+ labels: ['Desktop', 'Mobile', 'Tablet'],
412
+ datasets: [{
413
+ data: [45, 35, 20],
414
+ backgroundColor: [
415
+ 'rgba(59, 130, 246, 0.8)',
416
+ 'rgba(16, 185, 129, 0.8)',
417
+ 'rgba(245, 158, 11, 0.8)'
418
+ ]
419
+ }]
420
+ },
421
+ options: {
422
+ responsive: true,
423
+ plugins: {
424
+ legend: {
425
+ position: 'bottom'
426
+ }
427
+ }
428
+ }
429
+ });
430
+
431
+ // Add real-time updates simulation
432
+ function updateMetrics() {
433
+ const metrics = document.querySelectorAll('.bg-white.p-6');
434
+ metrics.forEach(metric => {
435
+ const value = metric.querySelector('.text-2xl');
436
+ if (value && Math.random() > 0.7) {
437
+ // Simulate small changes
438
+ const currentValue = parseFloat(value.textContent.replace(/[^0-9.]/g, ''));
439
+ const change = (Math.random() - 0.5) * 0.1;
440
+ const newValue = currentValue * (1 + change);
441
+ value.textContent = value.textContent.replace(/\d+/, Math.round(newValue));
442
+ }
443
+ });
444
+ }
445
+
446
+ // Update metrics every 5 seconds
447
+ setInterval(updateMetrics, 5000);
448
+ });`
449
+ }
450
+ }
451
+ };
452
+
453
+ const selectedTemplate = templates[template] || templates.vibecode;
454
+
455
+ // Enhance based on style preferences
456
+ let generatedHTML = selectedTemplate.structure.html;
457
+ let generatedJS = selectedTemplate.structure.js;
458
+
459
+ // Apply style modifications
460
+ if (style) {
461
+ if (style.includes('dark')) {
462
+ generatedHTML = generatedHTML
463
+ .replace(/bg-white/g, 'bg-gray-900')
464
+ .replace(/text-gray-900/g, 'text-white')
465
+ .replace(/text-gray-600/g, 'text-gray-300')
466
+ .replace(/text-gray-500/g, 'text-gray-400')
467
+ .replace(/bg-gray-50/g, 'bg-gray-800')
468
+ .replace(/border-gray-200/g, 'border-gray-700')
469
+ .replace(/shadow-sm/g, 'shadow-lg');
470
+ }
471
+
472
+ if (style.includes('colorful') || style.includes('rainbow')) {
473
+ generatedHTML = generatedHTML.replace(
474
+ 'background: linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
475
+ 'background: linear-gradient(135deg, #ff6b6b, #4ecdc4, #45b7d1, #96ceb4, #feca57)'
476
+ ).replace(
477
+ 'from-blue-600 to-purple-600',
478
+ 'from-red-500 via-yellow-500 to-green-500'
479
+ );
480
+ }
481
+ }
482
+
483
+ const fullHTML = generatedHTML.replace('</body>', `<script>${generatedJS}</script></body>`);
484
+
485
+ res.json({
486
+ success: true,
487
+ template: selectedTemplate.name,
488
+ html: generatedHTML,
489
+ js: generatedJS,
490
+ preview: fullHTML,
491
+ description: selectedTemplate.description
492
+ });
493
+
494
+ } catch (err) {
495
+ console.error('Template generation error:', err);
496
+ res.status(500).json({ error: 'Internal server error' });
497
+ }
498
+ });
499
+
500
+ // Serve static files AFTER API routes to ensure API routes take precedence
501
+ app.use(express.static(path.join(__dirname)));
502
+
503
+ app.listen(PORT, () => {
504
+ console.log(`VibeCode Local AI server running at http://localhost:${PORT}`);
505
+ console.log('API routes registered:');
506
+ console.log(' GET /api/health');
507
+ console.log(' POST /api/generate');
508
+ });
style.css CHANGED
@@ -1,28 +1,91 @@
1
- body {
2
- padding: 2rem;
3
- font-family: -apple-system, BlinkMacSystemFont, "Arial", sans-serif;
4
  }
5
 
6
- h1 {
7
- font-size: 16px;
8
- margin-top: 0;
 
 
 
 
 
 
 
 
9
  }
10
 
11
- p {
12
- color: rgb(107, 114, 128);
13
- font-size: 15px;
14
- margin-bottom: 10px;
15
- margin-top: 5px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
16
  }
17
 
18
- .card {
19
- max-width: 620px;
20
- margin: 0 auto;
21
- padding: 16px;
22
- border: 1px solid lightgray;
23
- border-radius: 16px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
24
  }
25
 
26
- .card p:last-child {
27
- margin-bottom: 0;
 
28
  }
 
 
 
 
 
 
 
1
+ /* Global light-mode baseline */
2
+ :root {
3
+ --radius: 0.75rem;
4
  }
5
 
6
+ /* Simple scrollbar for messages area */
7
+ #messages::-webkit-scrollbar {
8
+ width: 10px;
9
+ }
10
+ #messages::-webkit-scrollbar-thumb {
11
+ background: #e5e7eb;
12
+ border-radius: 9999px;
13
+ }
14
+ #messages {
15
+ scrollbar-width: thin;
16
+ scrollbar-color: #e5e7eb transparent;
17
  }
18
 
19
+ /* Message bubbles */
20
+ .message {
21
+ display: flex;
22
+ gap: 12px;
23
+ max-width: 860px;
24
+ }
25
+ .message .avatar {
26
+ flex: 0 0 auto;
27
+ width: 36px;
28
+ height: 36px;
29
+ border-radius: 9999px;
30
+ display: inline-flex;
31
+ align-items: center;
32
+ justify-content: center;
33
+ font-weight: 700;
34
+ font-size: 14px;
35
+ color: white;
36
+ }
37
+ .message.user .avatar {
38
+ background: linear-gradient(135deg, #0ea5e9, #6366f1);
39
+ }
40
+ .message.assistant .avatar {
41
+ background: linear-gradient(135deg, #22c55e, #14b8a6);
42
+ }
43
+ .message .bubble {
44
+ border: 1px solid #e5e7eb;
45
+ background: white;
46
+ padding: 12px 14px;
47
+ border-radius: 14px;
48
+ line-height: 1.55;
49
+ white-space: pre-wrap;
50
+ word-break: break-word;
51
+ }
52
+ .message.assistant .bubble {
53
+ background: #f8fafc;
54
  }
55
 
56
+ /* Mobile sidebar overlay */
57
+ .mobile-overlay {
58
+ position: fixed;
59
+ inset: 0;
60
+ background: rgba(0,0,0,0.35);
61
+ z-index: 40;
62
+ }
63
+ .mobile-sidebar {
64
+ position: fixed;
65
+ inset: 0 auto 0 0;
66
+ width: 80%;
67
+ max-width: 320px;
68
+ background: white;
69
+ z-index: 50;
70
+ transform: translateX(-100%);
71
+ transition: transform 0.25s ease-in-out;
72
+ }
73
+ .mobile-sidebar.open {
74
+ transform: translateX(0);
75
+ }
76
+ @media (min-width: 768px) {
77
+ .mobile-overlay, .mobile-sidebar {
78
+ display: none !important;
79
+ }
80
  }
81
 
82
+ /* Buttons subtle hover */
83
+ .btn {
84
+ transition: background 120ms ease-in-out, border-color 120ms ease-in-out;
85
  }
86
+
87
+ /* Small utilities */
88
+ .small-muted {
89
+ font-size: 12px;
90
+ color: #6b7280;
91
+ }