akhaliq HF Staff commited on
Commit
4b29926
·
1 Parent(s): 0423b7b

add landing page

Browse files
frontend/src/app/globals.css CHANGED
@@ -112,3 +112,17 @@ select:focus-visible {
112
  transition: background-color 0.2s ease, border-color 0.2s ease, transform 0.2s ease;
113
  }
114
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
112
  transition: background-color 0.2s ease, border-color 0.2s ease, transform 0.2s ease;
113
  }
114
 
115
+ /* Animation utilities */
116
+ @keyframes fade-in {
117
+ from {
118
+ opacity: 0;
119
+ }
120
+ to {
121
+ opacity: 1;
122
+ }
123
+ }
124
+
125
+ .animate-in {
126
+ animation: fade-in 0.3s ease-in-out;
127
+ }
128
+
frontend/src/app/page.tsx CHANGED
@@ -3,6 +3,7 @@
3
  import { useState, useEffect } from 'react';
4
  import { flushSync } from 'react-dom';
5
  import Header from '@/components/Header';
 
6
  import ChatInterface from '@/components/ChatInterface';
7
  import CodeEditor from '@/components/CodeEditor';
8
  import ControlPanel from '@/components/ControlPanel';
@@ -22,6 +23,9 @@ export default function Home() {
22
  const [currentRepoId, setCurrentRepoId] = useState<string | null>(null); // Track imported/deployed space
23
  const [username, setUsername] = useState<string | null>(null); // Track current user
24
 
 
 
 
25
  // Mobile view state: 'chat', 'editor', or 'settings'
26
  const [mobileView, setMobileView] = useState<'chat' | 'editor' | 'settings'>('editor');
27
 
@@ -34,6 +38,10 @@ export default function Home() {
34
  const parsed = JSON.parse(saved);
35
  console.log('[localStorage] Loaded messages from localStorage:', parsed.length, 'messages');
36
  setMessages(parsed);
 
 
 
 
37
  } catch (e) {
38
  console.error('[localStorage] Failed to parse saved messages:', e);
39
  }
@@ -79,12 +87,29 @@ export default function Home() {
79
  }
80
  };
81
 
82
- const handleSendMessage = async (message: string) => {
83
  if (!isAuthenticated) {
84
  alert('Please sign in with HuggingFace first! Click the "Sign in with Hugging Face" button in the header.');
85
  return;
86
  }
87
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
88
  // If there's existing code, include it in the message context for modifications
89
  let enhancedMessage = message;
90
  const hasRealCode = generatedCode &&
@@ -92,7 +117,7 @@ export default function Home() {
92
  !generatedCode.includes('Your generated code will appear here');
93
 
94
  if (hasRealCode) {
95
- enhancedMessage = `I have existing code in the editor. Please modify it based on my request.\n\nCurrent code:\n\`\`\`${selectedLanguage}\n${generatedCode}\n\`\`\`\n\nMy request: ${message}`;
96
  }
97
 
98
  // Add user message (show original message to user, but send enhanced to API)
@@ -110,8 +135,8 @@ export default function Home() {
110
  // Prepare request with enhanced query that includes current code
111
  const request: CodeGenerationRequest = {
112
  query: enhancedMessage,
113
- language: selectedLanguage,
114
- model_id: selectedModel,
115
  provider: 'auto',
116
  history: messages.map((m) => [m.role, m.content]),
117
  agent_mode: false,
@@ -384,6 +409,7 @@ export default function Home() {
384
  if (confirm('Clear all messages and code?')) {
385
  setMessages([]);
386
  setGeneratedCode('');
 
387
  // Clear localStorage to remove import history
388
  if (typeof window !== 'undefined') {
389
  localStorage.removeItem('anycoder_messages');
@@ -461,8 +487,30 @@ export default function Home() {
461
  setMobileView('editor');
462
  };
463
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
464
  return (
465
- <div className="h-screen flex flex-col bg-[#1d1d1f]">
466
  <Header />
467
 
468
  {/* VS Code layout with Apple styling - Responsive */}
 
3
  import { useState, useEffect } from 'react';
4
  import { flushSync } from 'react-dom';
5
  import Header from '@/components/Header';
6
+ import LandingPage from '@/components/LandingPage';
7
  import ChatInterface from '@/components/ChatInterface';
8
  import CodeEditor from '@/components/CodeEditor';
9
  import ControlPanel from '@/components/ControlPanel';
 
23
  const [currentRepoId, setCurrentRepoId] = useState<string | null>(null); // Track imported/deployed space
24
  const [username, setUsername] = useState<string | null>(null); // Track current user
25
 
26
+ // Landing page state - show landing page if no messages exist
27
+ const [showLandingPage, setShowLandingPage] = useState(true);
28
+
29
  // Mobile view state: 'chat', 'editor', or 'settings'
30
  const [mobileView, setMobileView] = useState<'chat' | 'editor' | 'settings'>('editor');
31
 
 
38
  const parsed = JSON.parse(saved);
39
  console.log('[localStorage] Loaded messages from localStorage:', parsed.length, 'messages');
40
  setMessages(parsed);
41
+ // If there are existing messages, show the full UI
42
+ if (parsed.length > 0) {
43
+ setShowLandingPage(false);
44
+ }
45
  } catch (e) {
46
  console.error('[localStorage] Failed to parse saved messages:', e);
47
  }
 
87
  }
88
  };
89
 
90
+ const handleSendMessage = async (message: string, overrideLanguage?: Language, overrideModel?: string) => {
91
  if (!isAuthenticated) {
92
  alert('Please sign in with HuggingFace first! Click the "Sign in with Hugging Face" button in the header.');
93
  return;
94
  }
95
 
96
+ // Hide landing page and show full UI when first message is sent
97
+ if (showLandingPage) {
98
+ setShowLandingPage(false);
99
+ }
100
+
101
+ // Use override values if provided, otherwise use state
102
+ const language = overrideLanguage || selectedLanguage;
103
+ const model = overrideModel || selectedModel;
104
+
105
+ // Update state if override values provided
106
+ if (overrideLanguage) {
107
+ setSelectedLanguage(overrideLanguage);
108
+ }
109
+ if (overrideModel) {
110
+ setSelectedModel(overrideModel);
111
+ }
112
+
113
  // If there's existing code, include it in the message context for modifications
114
  let enhancedMessage = message;
115
  const hasRealCode = generatedCode &&
 
117
  !generatedCode.includes('Your generated code will appear here');
118
 
119
  if (hasRealCode) {
120
+ enhancedMessage = `I have existing code in the editor. Please modify it based on my request.\n\nCurrent code:\n\`\`\`${language}\n${generatedCode}\n\`\`\`\n\nMy request: ${message}`;
121
  }
122
 
123
  // Add user message (show original message to user, but send enhanced to API)
 
135
  // Prepare request with enhanced query that includes current code
136
  const request: CodeGenerationRequest = {
137
  query: enhancedMessage,
138
+ language: language,
139
+ model_id: model,
140
  provider: 'auto',
141
  history: messages.map((m) => [m.role, m.content]),
142
  agent_mode: false,
 
409
  if (confirm('Clear all messages and code?')) {
410
  setMessages([]);
411
  setGeneratedCode('');
412
+ setShowLandingPage(true);
413
  // Clear localStorage to remove import history
414
  if (typeof window !== 'undefined') {
415
  localStorage.removeItem('anycoder_messages');
 
487
  setMobileView('editor');
488
  };
489
 
490
+ // Handle landing page prompt submission
491
+ const handleLandingPageStart = async (prompt: string, language: Language, modelId: string) => {
492
+ // Hide landing page immediately for smooth transition
493
+ setShowLandingPage(false);
494
+ // Send the message with the selected language and model
495
+ await handleSendMessage(prompt, language, modelId);
496
+ };
497
+
498
+ // Show landing page if no messages and showLandingPage is true
499
+ if (showLandingPage && messages.length === 0) {
500
+ return (
501
+ <div className="min-h-screen animate-in fade-in duration-300">
502
+ <LandingPage
503
+ onStart={handleLandingPageStart}
504
+ isAuthenticated={isAuthenticated}
505
+ initialLanguage={selectedLanguage}
506
+ initialModel={selectedModel}
507
+ />
508
+ </div>
509
+ );
510
+ }
511
+
512
  return (
513
+ <div className="h-screen flex flex-col bg-[#1d1d1f] animate-in fade-in duration-300">
514
  <Header />
515
 
516
  {/* VS Code layout with Apple styling - Responsive */}
frontend/src/components/ControlPanel.tsx CHANGED
@@ -1,6 +1,6 @@
1
  'use client';
2
 
3
- import { useState, useEffect } from 'react';
4
  import { apiClient } from '@/lib/api';
5
  import type { Model, Language } from '@/types';
6
 
@@ -32,11 +32,34 @@ export default function ControlPanel({
32
  const [importUrl, setImportUrl] = useState('');
33
  const [isImporting, setIsImporting] = useState(false);
34
  const [importError, setImportError] = useState<string | null>(null);
 
 
 
 
 
 
35
 
36
  useEffect(() => {
37
  loadData();
38
  }, []);
39
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
40
  const loadData = async () => {
41
  setIsLoading(true);
42
  await Promise.all([loadModels(), loadLanguages()]);
@@ -101,60 +124,128 @@ export default function ControlPanel({
101
  }
102
  };
103
 
 
 
 
 
 
 
104
  return (
105
- <div className="bg-[#28282a] p-5 space-y-6 h-full">
106
- <h3 className="text-base font-semibold text-[#e5e5e7] tracking-tight mb-2">Configuration</h3>
107
 
108
  {/* Language Selection */}
109
- <div>
110
- <label className="block text-sm font-semibold text-[#e5e5e7] mb-3 tracking-tight">
111
  Language
112
  </label>
113
- <select
114
- value={selectedLanguage}
115
- onChange={(e) => onLanguageChange(e.target.value as Language)}
 
 
 
116
  disabled={isGenerating || isLoading}
117
- className="w-full px-4 py-3 bg-[#3a3a3c] text-[#e5e5e7] text-sm border border-[#48484a] rounded-xl focus:outline-none focus:ring-2 focus:ring-[#007aff] focus:border-transparent disabled:opacity-50 font-medium shadow-sm"
118
  >
119
- {isLoading ? (
120
- <option value="">Loading...</option>
121
- ) : languages.length === 0 ? (
122
- <option value="">No languages available</option>
123
- ) : (
124
- languages.map((lang) => (
125
- <option key={lang} value={lang} className="bg-[#3a3a3c]">
126
- {lang === 'html' ? 'HTML' : lang.charAt(0).toUpperCase() + lang.slice(1)}
127
- </option>
128
- ))
129
- )}
130
- </select>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
131
  </div>
132
 
133
  {/* Model Selection */}
134
- <div>
135
- <label className="block text-sm font-semibold text-[#e5e5e7] mb-3 tracking-tight">
136
  AI Model
137
  </label>
138
- <select
139
- value={selectedModel}
140
- onChange={(e) => onModelChange(e.target.value)}
 
 
 
141
  disabled={isGenerating || isLoading}
142
- className="w-full px-4 py-3 bg-[#3a3a3c] text-[#e5e5e7] text-sm border border-[#48484a] rounded-xl focus:outline-none focus:ring-2 focus:ring-[#007aff] focus:border-transparent disabled:opacity-50 font-medium shadow-sm"
143
  >
144
- {isLoading ? (
145
- <option value="">Loading...</option>
146
- ) : models.length === 0 ? (
147
- <option value="">No models available</option>
148
- ) : (
149
- models.map((model) => (
150
- <option key={model.id} value={model.id} className="bg-[#3a3a3c]">
151
- {model.name}
152
- </option>
153
- ))
154
- )}
155
- </select>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
156
  {!isLoading && models.find(m => m.id === selectedModel) && (
157
- <p className="text-xs text-[#86868b] mt-3 leading-relaxed">
158
  {models.find(m => m.id === selectedModel)?.description}
159
  </p>
160
  )}
@@ -257,4 +348,3 @@ export default function ControlPanel({
257
  </div>
258
  );
259
  }
260
-
 
1
  'use client';
2
 
3
+ import { useState, useEffect, useRef } from 'react';
4
  import { apiClient } from '@/lib/api';
5
  import type { Model, Language } from '@/types';
6
 
 
32
  const [importUrl, setImportUrl] = useState('');
33
  const [isImporting, setIsImporting] = useState(false);
34
  const [importError, setImportError] = useState<string | null>(null);
35
+
36
+ // Dropdown states
37
+ const [showLanguageDropdown, setShowLanguageDropdown] = useState(false);
38
+ const [showModelDropdown, setShowModelDropdown] = useState(false);
39
+ const languageDropdownRef = useRef<HTMLDivElement>(null);
40
+ const modelDropdownRef = useRef<HTMLDivElement>(null);
41
 
42
  useEffect(() => {
43
  loadData();
44
  }, []);
45
 
46
+ // Close dropdowns when clicking outside
47
+ useEffect(() => {
48
+ const handleClickOutside = (event: MouseEvent) => {
49
+ if (languageDropdownRef.current && !languageDropdownRef.current.contains(event.target as Node)) {
50
+ setShowLanguageDropdown(false);
51
+ }
52
+ if (modelDropdownRef.current && !modelDropdownRef.current.contains(event.target as Node)) {
53
+ setShowModelDropdown(false);
54
+ }
55
+ };
56
+
57
+ document.addEventListener('mousedown', handleClickOutside);
58
+ return () => {
59
+ document.removeEventListener('mousedown', handleClickOutside);
60
+ };
61
+ }, []);
62
+
63
  const loadData = async () => {
64
  setIsLoading(true);
65
  await Promise.all([loadModels(), loadLanguages()]);
 
124
  }
125
  };
126
 
127
+ const formatLanguageName = (lang: Language) => {
128
+ if (lang === 'html') return 'HTML';
129
+ if (lang === 'transformers.js') return 'Transformers.js';
130
+ return lang.charAt(0).toUpperCase() + lang.slice(1);
131
+ };
132
+
133
  return (
134
+ <div className="bg-[#252526] p-6 space-y-6 h-full">
135
+ <h3 className="text-2xl font-bold text-[#cccccc] tracking-tight mb-6">Configuration</h3>
136
 
137
  {/* Language Selection */}
138
+ <div className="relative" ref={languageDropdownRef}>
139
+ <label className="block text-sm font-semibold text-[#cccccc] mb-3 tracking-tight">
140
  Language
141
  </label>
142
+ <button
143
+ type="button"
144
+ onClick={() => {
145
+ setShowLanguageDropdown(!showLanguageDropdown);
146
+ setShowModelDropdown(false);
147
+ }}
148
  disabled={isGenerating || isLoading}
149
+ className="w-full px-4 py-3 bg-[#3a3a3c] text-[#cccccc] text-sm border border-[#3e3e42] rounded-lg focus:outline-none focus:ring-2 focus:ring-[#007acc] focus:border-transparent disabled:opacity-50 font-medium shadow-sm flex items-center justify-between hover:bg-[#404040] transition-colors"
150
  >
151
+ <span>{isLoading ? 'Loading...' : formatLanguageName(selectedLanguage)}</span>
152
+ <svg
153
+ className={`w-4 h-4 text-[#cccccc] transition-transform ${showLanguageDropdown ? 'rotate-180' : ''}`}
154
+ fill="none"
155
+ stroke="currentColor"
156
+ viewBox="0 0 24 24"
157
+ >
158
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
159
+ </svg>
160
+ </button>
161
+
162
+ {/* Language Dropdown Tray */}
163
+ {showLanguageDropdown && !isLoading && languages.length > 0 && (
164
+ <div className="absolute z-50 w-full mt-2 bg-[#252526] border border-[#3e3e42] rounded-lg shadow-2xl shadow-black/50 overflow-hidden">
165
+ <div className="max-h-64 overflow-y-auto">
166
+ {languages.map((lang) => (
167
+ <button
168
+ key={lang}
169
+ type="button"
170
+ onClick={() => {
171
+ onLanguageChange(lang);
172
+ setShowLanguageDropdown(false);
173
+ }}
174
+ className={`w-full px-4 py-3 text-left text-sm text-[#cccccc] hover:bg-[#2a2d2e] transition-colors ${
175
+ selectedLanguage === lang ? 'bg-[#264f78] hover:bg-[#264f78]' : ''
176
+ }`}
177
+ >
178
+ {formatLanguageName(lang)}
179
+ </button>
180
+ ))}
181
+ </div>
182
+ </div>
183
+ )}
184
  </div>
185
 
186
  {/* Model Selection */}
187
+ <div className="relative" ref={modelDropdownRef}>
188
+ <label className="block text-sm font-semibold text-[#cccccc] mb-3 tracking-tight">
189
  AI Model
190
  </label>
191
+ <button
192
+ type="button"
193
+ onClick={() => {
194
+ setShowModelDropdown(!showModelDropdown);
195
+ setShowLanguageDropdown(false);
196
+ }}
197
  disabled={isGenerating || isLoading}
198
+ className="w-full px-4 py-3 bg-[#3a3a3c] text-[#cccccc] text-sm border border-[#3e3e42] rounded-lg focus:outline-none focus:ring-2 focus:ring-[#007acc] focus:border-transparent disabled:opacity-50 font-medium shadow-sm flex items-center justify-between hover:bg-[#404040] transition-colors"
199
  >
200
+ <span>
201
+ {isLoading
202
+ ? 'Loading...'
203
+ : models.find(m => m.id === selectedModel)?.name || 'Select model'
204
+ }
205
+ </span>
206
+ <svg
207
+ className={`w-4 h-4 text-[#cccccc] transition-transform ${showModelDropdown ? 'rotate-180' : ''}`}
208
+ fill="none"
209
+ stroke="currentColor"
210
+ viewBox="0 0 24 24"
211
+ >
212
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
213
+ </svg>
214
+ </button>
215
+
216
+ {/* Model Dropdown Tray */}
217
+ {showModelDropdown && !isLoading && models.length > 0 && (
218
+ <div className="absolute z-50 w-full mt-2 bg-[#252526] border border-[#3e3e42] rounded-lg shadow-2xl shadow-black/50 overflow-hidden">
219
+ <div className="max-h-80 overflow-y-auto">
220
+ {models.map((model) => (
221
+ <button
222
+ key={model.id}
223
+ type="button"
224
+ onClick={() => {
225
+ onModelChange(model.id);
226
+ setShowModelDropdown(false);
227
+ }}
228
+ className={`w-full px-4 py-3 text-left transition-colors ${
229
+ selectedModel === model.id
230
+ ? 'bg-[#264f78] hover:bg-[#264f78]'
231
+ : 'hover:bg-[#2a2d2e]'
232
+ }`}
233
+ >
234
+ <div className="text-sm font-medium text-[#cccccc]">{model.name}</div>
235
+ {model.description && (
236
+ <div className="text-xs text-[#858585] mt-1 leading-relaxed">
237
+ {model.description}
238
+ </div>
239
+ )}
240
+ </button>
241
+ ))}
242
+ </div>
243
+ </div>
244
+ )}
245
+
246
+ {/* Model Description */}
247
  {!isLoading && models.find(m => m.id === selectedModel) && (
248
+ <p className="text-xs text-[#858585] mt-3 leading-relaxed">
249
  {models.find(m => m.id === selectedModel)?.description}
250
  </p>
251
  )}
 
348
  </div>
349
  );
350
  }
 
frontend/src/components/LandingPage.tsx ADDED
@@ -0,0 +1,267 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 'use client';
2
+
3
+ import { useState, useEffect, useRef } from 'react';
4
+ import { apiClient } from '@/lib/api';
5
+ import type { Model, Language } from '@/types';
6
+
7
+ interface LandingPageProps {
8
+ onStart: (prompt: string, language: Language, modelId: string) => void;
9
+ isAuthenticated: boolean;
10
+ initialLanguage?: Language;
11
+ initialModel?: string;
12
+ }
13
+
14
+ export default function LandingPage({
15
+ onStart,
16
+ isAuthenticated,
17
+ initialLanguage = 'html',
18
+ initialModel = 'gemini-3.0-pro'
19
+ }: LandingPageProps) {
20
+ const [prompt, setPrompt] = useState('');
21
+ const [selectedLanguage, setSelectedLanguage] = useState<Language>(initialLanguage);
22
+ const [selectedModel, setSelectedModel] = useState<string>(initialModel);
23
+ const [models, setModels] = useState<Model[]>([]);
24
+ const [languages, setLanguages] = useState<Language[]>([]);
25
+ const [isLoading, setIsLoading] = useState(true);
26
+
27
+ // Dropdown states
28
+ const [showLanguageDropdown, setShowLanguageDropdown] = useState(false);
29
+ const [showModelDropdown, setShowModelDropdown] = useState(false);
30
+ const languageDropdownRef = useRef<HTMLDivElement>(null);
31
+ const modelDropdownRef = useRef<HTMLDivElement>(null);
32
+
33
+ useEffect(() => {
34
+ loadData();
35
+ }, []);
36
+
37
+ // Close dropdowns when clicking outside
38
+ useEffect(() => {
39
+ const handleClickOutside = (event: MouseEvent) => {
40
+ if (languageDropdownRef.current && !languageDropdownRef.current.contains(event.target as Node)) {
41
+ setShowLanguageDropdown(false);
42
+ }
43
+ if (modelDropdownRef.current && !modelDropdownRef.current.contains(event.target as Node)) {
44
+ setShowModelDropdown(false);
45
+ }
46
+ };
47
+
48
+ document.addEventListener('mousedown', handleClickOutside);
49
+ return () => {
50
+ document.removeEventListener('mousedown', handleClickOutside);
51
+ };
52
+ }, []);
53
+
54
+ const loadData = async () => {
55
+ setIsLoading(true);
56
+ await Promise.all([loadModels(), loadLanguages()]);
57
+ setIsLoading(false);
58
+ };
59
+
60
+ const loadModels = async () => {
61
+ try {
62
+ const modelsList = await apiClient.getModels();
63
+ setModels(modelsList);
64
+ } catch (error) {
65
+ console.error('Failed to load models:', error);
66
+ }
67
+ };
68
+
69
+ const loadLanguages = async () => {
70
+ try {
71
+ const { languages: languagesList } = await apiClient.getLanguages();
72
+ setLanguages(languagesList);
73
+ } catch (error) {
74
+ console.error('Failed to load languages:', error);
75
+ }
76
+ };
77
+
78
+ const handleSubmit = (e: React.FormEvent) => {
79
+ e.preventDefault();
80
+ if (prompt.trim() && isAuthenticated) {
81
+ onStart(prompt.trim(), selectedLanguage, selectedModel);
82
+ } else if (!isAuthenticated) {
83
+ alert('Please sign in with HuggingFace first!');
84
+ }
85
+ };
86
+
87
+ const formatLanguageName = (lang: Language) => {
88
+ if (lang === 'html') return 'HTML';
89
+ if (lang === 'transformers.js') return 'Transformers.js';
90
+ return lang.charAt(0).toUpperCase() + lang.slice(1);
91
+ };
92
+
93
+ return (
94
+ <div className="min-h-screen flex flex-col bg-[#1e1e1e] overflow-y-auto">
95
+ {/* Header - Minimal Apple style */}
96
+ <header className="flex items-center px-8 py-4 border-b border-[#3e3e42]/30 flex-shrink-0">
97
+ <h1 className="text-base font-semibold text-[#cccccc] tracking-tight">
98
+ AnyCoder
99
+ </h1>
100
+ </header>
101
+
102
+ {/* Main Content - Centered with generous Apple spacing */}
103
+ <main className="flex-1 flex items-center justify-center px-6 md:px-8 py-8 md:py-12 min-h-0">
104
+ <div className="w-full max-w-4xl">
105
+ {/* Headline - Apple typography with VS Code colors */}
106
+ <div className="text-center mb-8 md:mb-10">
107
+ <h2 className="text-4xl md:text-5xl lg:text-6xl font-bold text-[#cccccc] mb-4 tracking-[-0.02em] leading-[1.1]">
108
+ Build with AnyCoder
109
+ </h2>
110
+ <p className="text-base md:text-lg text-[#858585] font-light tracking-tight">
111
+ Create apps and websites by chatting with AI
112
+ </p>
113
+ </div>
114
+
115
+ {/* Prompt Input - VS Code style with Apple polish */}
116
+ <form onSubmit={handleSubmit} className="relative">
117
+ <div className="relative bg-[#252526] rounded-2xl border border-[#3e3e42] overflow-hidden shadow-2xl shadow-black/60">
118
+ {/* Options Row - Language and Model dropdowns */}
119
+ <div className="flex items-center gap-3 px-6 pt-5 pb-4 border-b border-[#3e3e42]/40">
120
+ {/* Language Dropdown */}
121
+ <div className="relative flex-1" ref={languageDropdownRef}>
122
+ <button
123
+ type="button"
124
+ onClick={() => {
125
+ setShowLanguageDropdown(!showLanguageDropdown);
126
+ setShowModelDropdown(false);
127
+ }}
128
+ disabled={isLoading}
129
+ className="w-full px-4 py-2.5 bg-[#2d2d30] text-[#cccccc] text-sm border border-[#3e3e42] rounded-lg focus:outline-none focus:ring-2 focus:ring-[#007acc]/50 focus:border-[#007acc] disabled:opacity-50 font-medium flex items-center justify-between hover:bg-[#323233] transition-all duration-150"
130
+ >
131
+ <span className="text-[#cccccc]">{isLoading ? 'Loading...' : formatLanguageName(selectedLanguage)}</span>
132
+ <svg
133
+ className={`w-4 h-4 text-[#858585] transition-transform duration-200 ${showLanguageDropdown ? 'rotate-180' : ''}`}
134
+ fill="none"
135
+ stroke="currentColor"
136
+ viewBox="0 0 24 24"
137
+ >
138
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
139
+ </svg>
140
+ </button>
141
+
142
+ {/* Language Dropdown Tray */}
143
+ {showLanguageDropdown && !isLoading && languages.length > 0 && (
144
+ <div className="absolute z-50 w-full mt-1.5 bg-[#252526] border border-[#3e3e42] rounded-lg shadow-2xl shadow-black/70 overflow-hidden">
145
+ <div className="max-h-64 overflow-y-auto">
146
+ {languages.map((lang) => (
147
+ <button
148
+ key={lang}
149
+ type="button"
150
+ onClick={() => {
151
+ setSelectedLanguage(lang);
152
+ setShowLanguageDropdown(false);
153
+ }}
154
+ className={`w-full px-4 py-2.5 text-left text-sm text-[#cccccc] hover:bg-[#2a2d2e] transition-colors duration-150 ${
155
+ selectedLanguage === lang ? 'bg-[#264f78] hover:bg-[#264f78] text-[#ffffff]' : ''
156
+ }`}
157
+ >
158
+ {formatLanguageName(lang)}
159
+ </button>
160
+ ))}
161
+ </div>
162
+ </div>
163
+ )}
164
+ </div>
165
+
166
+ {/* Model Dropdown */}
167
+ <div className="relative flex-1" ref={modelDropdownRef}>
168
+ <button
169
+ type="button"
170
+ onClick={() => {
171
+ setShowModelDropdown(!showModelDropdown);
172
+ setShowLanguageDropdown(false);
173
+ }}
174
+ disabled={isLoading}
175
+ className="w-full px-4 py-2.5 bg-[#2d2d30] text-[#cccccc] text-sm border border-[#3e3e42] rounded-lg focus:outline-none focus:ring-2 focus:ring-[#007acc]/50 focus:border-[#007acc] disabled:opacity-50 font-medium flex items-center justify-between hover:bg-[#323233] transition-all duration-150"
176
+ >
177
+ <span className="truncate text-[#cccccc]">
178
+ {isLoading
179
+ ? 'Loading...'
180
+ : models.find(m => m.id === selectedModel)?.name || 'Select model'
181
+ }
182
+ </span>
183
+ <svg
184
+ className={`w-4 h-4 text-[#858585] transition-transform duration-200 flex-shrink-0 ml-2 ${showModelDropdown ? 'rotate-180' : ''}`}
185
+ fill="none"
186
+ stroke="currentColor"
187
+ viewBox="0 0 24 24"
188
+ >
189
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
190
+ </svg>
191
+ </button>
192
+
193
+ {/* Model Dropdown Tray */}
194
+ {showModelDropdown && !isLoading && models.length > 0 && (
195
+ <div className="absolute z-50 w-full mt-1.5 bg-[#252526] border border-[#3e3e42] rounded-lg shadow-2xl shadow-black/70 overflow-hidden">
196
+ <div className="max-h-80 overflow-y-auto">
197
+ {models.map((model) => (
198
+ <button
199
+ key={model.id}
200
+ type="button"
201
+ onClick={() => {
202
+ setSelectedModel(model.id);
203
+ setShowModelDropdown(false);
204
+ }}
205
+ className={`w-full px-4 py-3 text-left transition-colors duration-150 ${
206
+ selectedModel === model.id
207
+ ? 'bg-[#264f78] hover:bg-[#264f78]'
208
+ : 'hover:bg-[#2a2d2e]'
209
+ }`}
210
+ >
211
+ <div className="text-sm font-medium text-[#cccccc]">{model.name}</div>
212
+ {model.description && (
213
+ <div className="text-xs text-[#858585] mt-1 leading-relaxed">
214
+ {model.description}
215
+ </div>
216
+ )}
217
+ </button>
218
+ ))}
219
+ </div>
220
+ </div>
221
+ )}
222
+ </div>
223
+ </div>
224
+
225
+ {/* Textarea */}
226
+ <textarea
227
+ value={prompt}
228
+ onChange={(e) => setPrompt(e.target.value)}
229
+ placeholder="Ask AnyCoder to create a landing page for my..."
230
+ className="w-full px-6 py-5 text-base md:text-lg text-[#cccccc] bg-transparent placeholder:text-[#858585] resize-none focus:outline-none min-h-[120px] md:min-h-[140px] font-normal leading-relaxed"
231
+ rows={3}
232
+ onKeyDown={(e) => {
233
+ if (e.key === 'Enter' && (e.metaKey || e.ctrlKey)) {
234
+ handleSubmit(e);
235
+ }
236
+ }}
237
+ />
238
+
239
+ {/* Input Controls - Apple style button */}
240
+ <div className="flex items-center justify-end px-6 pb-5 pt-4 border-t border-[#3e3e42]/40">
241
+ <button
242
+ type="submit"
243
+ disabled={!prompt.trim() || !isAuthenticated}
244
+ className="px-8 py-3 bg-[#007acc] text-white text-sm font-semibold rounded-lg hover:bg-[#0066b3] disabled:opacity-40 disabled:cursor-not-allowed transition-all duration-200 shadow-lg shadow-[#007acc]/20 hover:shadow-[#007acc]/30 active:scale-[0.97] flex items-center space-x-2 tracking-tight"
245
+ title="Send (⌘+Enter)"
246
+ >
247
+ <span>Send</span>
248
+ <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" strokeWidth={2.5}>
249
+ <path strokeLinecap="round" strokeLinejoin="round" d="M12 19l9 2-9-18-9 18 9-2zm0 0v-8" />
250
+ </svg>
251
+ </button>
252
+ </div>
253
+ </div>
254
+
255
+ {!isAuthenticated && (
256
+ <div className="mt-6 text-center">
257
+ <p className="text-sm text-[#858585] font-medium">
258
+ Please sign in to get started
259
+ </p>
260
+ </div>
261
+ )}
262
+ </form>
263
+ </div>
264
+ </main>
265
+ </div>
266
+ );
267
+ }