File size: 44,568 Bytes
b42dfef
 
a4a6500
4748143
b42dfef
4b29926
b42dfef
 
 
 
 
 
 
 
35169fd
 
106a0d6
b42dfef
 
bc202f9
b42dfef
 
c66426d
 
6b76117
9ffda57
75503d6
4b29926
 
 
7f9535b
 
41493fc
 
 
 
 
 
 
b42dfef
9be4ecd
 
 
 
 
a23307a
 
 
 
 
 
 
 
 
35169fd
106a0d6
 
35169fd
 
 
 
 
 
4b29926
 
 
 
35169fd
 
 
 
41493fc
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
35169fd
 
 
 
 
 
106a0d6
 
 
 
 
e6104d9
 
3c247ba
 
e6104d9
a4a6500
b42dfef
 
a4a6500
 
 
 
 
3c247ba
e6104d9
3c247ba
e6104d9
a4a6500
 
 
 
e6104d9
a4a6500
 
 
3c247ba
 
 
 
 
a4a6500
 
 
 
 
 
 
 
4d53e2b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
a4a6500
3c247ba
a4a6500
 
3c247ba
 
 
 
 
 
 
a4a6500
 
 
 
 
b42dfef
 
c66426d
b42dfef
 
 
e6104d9
b42dfef
 
 
 
c66426d
3c247ba
 
 
e6104d9
a4a6500
 
 
 
3c247ba
a4a6500
 
3c247ba
e6104d9
 
 
 
 
 
 
 
 
 
3c247ba
 
 
 
 
 
 
a4a6500
3c247ba
a4a6500
c66426d
 
e6104d9
 
 
 
 
 
 
3c247ba
b42dfef
a4a6500
3c247ba
e6104d9
a4a6500
 
 
e6104d9
3c247ba
b42dfef
 
 
a21948d
b42dfef
 
 
 
 
4b29926
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ddf45da
 
 
 
 
 
 
4b29926
ddf45da
 
 
b42dfef
 
 
 
 
 
 
f26c666
 
 
b42dfef
ddf45da
e2918f7
 
 
 
 
 
 
 
 
b42dfef
ddf45da
4b29926
 
b42dfef
 
 
e2918f7
a21948d
b42dfef
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4748143
b42dfef
7523755
4748143
 
 
 
 
 
 
f26c666
b42dfef
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6b76117
 
9ffda57
 
 
 
 
 
 
 
 
 
6b76117
b42dfef
 
 
 
 
 
 
 
 
 
 
 
cd2780c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1866adf
cd2780c
 
 
 
1866adf
cd2780c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
b42dfef
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6b76117
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
b42dfef
35169fd
 
 
 
 
b42dfef
da59e40
b42dfef
 
 
a0e6952
89d579c
 
a0e6952
d017d01
 
 
89d579c
d017d01
a0e6952
d017d01
 
a0e6952
 
d017d01
 
 
47565e8
 
c66426d
47565e8
31713bf
 
89d579c
d017d01
31713bf
 
89d579c
d017d01
 
47565e8
d017d01
 
 
47565e8
31713bf
 
d017d01
31713bf
47565e8
d017d01
 
 
 
 
 
 
 
 
 
47565e8
d017d01
 
 
 
 
 
 
 
47565e8
 
d017d01
 
 
47565e8
 
 
d017d01
89d579c
31713bf
d017d01
89d579c
47565e8
d017d01
47565e8
 
d017d01
 
47565e8
 
 
c66426d
31713bf
 
 
 
d017d01
89d579c
c66426d
31713bf
c66426d
d017d01
 
 
 
89d579c
d017d01
da59e40
 
b42dfef
 
47565e8
89d579c
47565e8
 
deb8b94
 
 
 
 
 
 
47565e8
fe566fd
d1b315f
 
b42dfef
 
deb8b94
da59e40
 
d1b315f
 
 
 
 
 
 
 
 
47565e8
 
 
 
35169fd
3aef00d
 
47565e8
35169fd
da59e40
 
35169fd
b42dfef
 
c66426d
 
fe566fd
c66426d
fe566fd
 
 
 
 
 
 
c66426d
 
deb8b94
c66426d
 
47565e8
deb8b94
 
c66426d
 
 
 
b42dfef
 
 
 
 
 
 
47565e8
c66426d
da59e40
b42dfef
 
 
 
35169fd
 
 
 
 
 
 
 
 
 
 
b42dfef
 
 
 
 
 
 
4b29926
106a0d6
 
 
 
 
b42dfef
 
 
c66426d
da59e40
 
 
 
 
 
1280ed3
 
 
 
 
3eb00a5
 
 
c66426d
 
 
fe566fd
da59e40
c66426d
 
da59e40
 
 
 
 
 
 
 
 
c66426d
 
e2918f7
c66426d
e2918f7
 
c66426d
 
 
da59e40
 
 
 
 
c66426d
fe566fd
 
c66426d
fe566fd
 
c66426d
 
da59e40
 
ddf45da
 
 
c66426d
 
 
ddf45da
 
 
 
3eb00a5
da59e40
3eb00a5
 
ddf45da
 
3eb00a5
 
 
 
 
4b29926
6b76117
4b29926
 
6b76117
9ffda57
6b76117
 
9ffda57
 
 
6b76117
 
4b29926
6b76117
a21948d
4b29926
 
41493fc
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4b29926
 
 
 
 
 
1280ed3
4b29926
 
 
27c7a45
c8820b9
 
4b29926
 
 
 
 
b42dfef
04c818a
b42dfef
 
04c818a
75503d6
 
41493fc
 
 
 
 
 
 
 
 
 
 
b42dfef
04c818a
 
b42dfef
 
 
 
 
 
 
 
 
 
 
 
 
41493fc
 
 
 
 
 
 
75503d6
 
 
04c818a
75503d6
41493fc
 
75503d6
b42dfef
04c818a
b42dfef
04c818a
0423b7b
 
 
 
 
 
b42dfef
 
04c818a
b42dfef
04c818a
 
 
b42dfef
 
04c818a
b42dfef
 
 
 
 
 
 
 
 
 
 
 
 
 
41493fc
 
 
 
 
 
 
75503d6
41493fc
 
 
 
 
 
 
 
 
 
 
 
b42dfef
 
 
 
 
 
3eb00a5
b42dfef
 
 
 
 
75503d6
04c818a
75503d6
 
04c818a
75503d6
04c818a
 
75503d6
 
04c818a
 
75503d6
04c818a
75503d6
 
 
 
04c818a
75503d6
04c818a
 
75503d6
 
04c818a
 
75503d6
04c818a
75503d6
 
 
 
04c818a
75503d6
04c818a
 
75503d6
 
04c818a
 
 
75503d6
04c818a
75503d6
 
 
 
04c818a
 
 
 
b42dfef
 
 
 
 
 
 
 
 
 
 
 
 
04c818a
b42dfef
 
 
 
 
 
 
e6104d9
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
'use client';

import { useState, useEffect, useRef } from 'react';
import { flushSync } from 'react-dom';
import Header from '@/components/Header';
import LandingPage from '@/components/LandingPage';
import ChatInterface from '@/components/ChatInterface';
import CodeEditor from '@/components/CodeEditor';
import ControlPanel from '@/components/ControlPanel';
import { apiClient } from '@/lib/api';
import { isAuthenticated as checkIsAuthenticated, getStoredToken } from '@/lib/auth';
import type { Message, Language, CodeGenerationRequest } from '@/types';

export default function Home() {
  // Initialize messages as empty array (will load from localStorage in useEffect)
  const [messages, setMessages] = useState<Message[]>([]);
  
  const [generatedCode, setGeneratedCode] = useState('');
  const [selectedLanguage, setSelectedLanguage] = useState<Language>('html');
  const [selectedModel, setSelectedModel] = useState('deepseek-ai/DeepSeek-V3.2-Exp');
  const [isGenerating, setIsGenerating] = useState(false);
  const [isAuthenticated, setIsAuthenticated] = useState(false);
  const [currentRepoId, setCurrentRepoId] = useState<string | null>(null);  // Track imported/deployed space
  const [username, setUsername] = useState<string | null>(null);  // Track current user
  const [pendingPR, setPendingPR] = useState<{ repoId: string; language: Language } | null>(null);  // Track pending PR after redesign
  const pendingPRRef = useRef<{ repoId: string; language: Language } | null>(null);  // Ref for immediate access
  
  // Landing page state - show landing page if no messages exist
  const [showLandingPage, setShowLandingPage] = useState(true);
  
  // Mobile view state: 'chat', 'editor', or 'settings' - start on chat for mobile
  const [mobileView, setMobileView] = useState<'chat' | 'editor' | 'settings'>('chat');
  
  // Resizable sidebar widths (in pixels)
  const [chatSidebarWidth, setChatSidebarWidth] = useState(320);
  const [settingsSidebarWidth, setSettingsSidebarWidth] = useState(288);
  const [isResizingChat, setIsResizingChat] = useState(false);
  const [isResizingSettings, setIsResizingSettings] = useState(false);
  const [isDesktop, setIsDesktop] = useState(false);

  // Debug: Log currentRepoId changes
  useEffect(() => {
    console.log('[App] πŸ”΅ currentRepoId changed to:', currentRepoId);
  }, [currentRepoId]);

  // Clear cache on app startup to ensure fresh data
  useEffect(() => {
    if (typeof window !== 'undefined') {
      console.log('[Cache] Clearing models and languages cache on app startup');
      localStorage.removeItem('anycoder_models');
      localStorage.removeItem('anycoder_languages');
    }
  }, []); // Run once on mount

  // Load messages from localStorage on mount (client-side only to avoid hydration issues)
  useEffect(() => {
    if (typeof window !== 'undefined') {
      const saved = localStorage.getItem('anycoder_messages');
      if (saved) {
        try {
          const parsed = JSON.parse(saved);
          console.log('[localStorage] Loaded messages from localStorage:', parsed.length, 'messages');
          setMessages(parsed);
          // If there are existing messages, show the full UI
          if (parsed.length > 0) {
            setShowLandingPage(false);
          }
        } catch (e) {
          console.error('[localStorage] Failed to parse saved messages:', e);
        }
      }
      
      // Load sidebar widths from localStorage
      const savedChatWidth = localStorage.getItem('anycoder_chat_sidebar_width');
      const savedSettingsWidth = localStorage.getItem('anycoder_settings_sidebar_width');
      if (savedChatWidth) {
        setChatSidebarWidth(parseInt(savedChatWidth, 10));
      }
      if (savedSettingsWidth) {
        setSettingsSidebarWidth(parseInt(savedSettingsWidth, 10));
      }
      
      // Check if desktop on mount
      const checkDesktop = () => {
        setIsDesktop(window.innerWidth >= 768);
      };
      checkDesktop();
      
      // Listen for window resize to update desktop status
      window.addEventListener('resize', checkDesktop);
      return () => window.removeEventListener('resize', checkDesktop);
    }
  }, []); // Empty deps = run once on mount

  // Save messages to localStorage whenever they change (CRITICAL FOR PERSISTENCE!)
  useEffect(() => {
    if (typeof window !== 'undefined' && messages.length > 0) {
      localStorage.setItem('anycoder_messages', JSON.stringify(messages));
      console.log('[localStorage] Saved', messages.length, 'messages to localStorage');
    }
  }, [messages]);

  // Track if we've attempted to fetch username to avoid repeated failures
  const usernameFetchAttemptedRef = useRef(false);
  // Track if backend appears to be unavailable (to avoid repeated failed requests)
  const backendUnavailableRef = useRef(false);

  // Check auth on mount and handle OAuth callback
  useEffect(() => {
    checkAuth();
    
    // Check for OAuth callback in URL (handles ?session=token)
    // initializeOAuth already handles this, but we call checkAuth to sync state
    const urlParams = new URLSearchParams(window.location.search);
    if (urlParams.get('session')) {
      // OAuth callback - reset both flags and check auth after a brief delay
      usernameFetchAttemptedRef.current = false;
      backendUnavailableRef.current = false; // Reset backend status on OAuth callback
      setTimeout(() => checkAuth(), 200);
    }
  }, []); // Only run once on mount

  // Listen for storage changes (e.g., logout from another tab)
  // Note: storage events only fire in OTHER tabs, not the current one
  useEffect(() => {
    const handleStorageChange = (e: StorageEvent) => {
      if (e.key === 'hf_oauth_token' || e.key === 'hf_user_info') {
        // Only reset username fetch if we have a token (might be logging in)
        if (e.newValue) {
          usernameFetchAttemptedRef.current = false;
          backendUnavailableRef.current = false; // Reset backend status on login
        }
        checkAuth();
      }
    };

    window.addEventListener('storage', handleStorageChange);
    return () => window.removeEventListener('storage', handleStorageChange);
  }, []);

  // Listen for authentication expiration events
  useEffect(() => {
    const handleAuthExpired = (e: CustomEvent) => {
      console.log('[Auth] Session expired:', e.detail?.message);
      // Clear authentication state
      setIsAuthenticated(false);
      setUsername(null);
      apiClient.setToken(null);
      
      // Show alert to user
      if (typeof window !== 'undefined') {
        alert(e.detail?.message || 'Your session has expired. Please sign in again.');
      }
    };

    window.addEventListener('auth-expired', handleAuthExpired as EventListener);
    return () => window.removeEventListener('auth-expired', handleAuthExpired as EventListener);
  }, []);

  // Listen for window focus (user returns to tab after OAuth redirect)
  // Only check if backend was available before or if we're authenticated with token
  useEffect(() => {
    const handleFocus = () => {
      // Only reset and check if we're authenticated (might have logged in elsewhere)
      // Don't reset if backend is known to be unavailable and we're not authenticated
      const authenticated = checkIsAuthenticated();
      if (authenticated) {
        usernameFetchAttemptedRef.current = false;
        backendUnavailableRef.current = false; // Reset backend status - might be back up
      }
      checkAuth();
    };

    window.addEventListener('focus', handleFocus);
    return () => window.removeEventListener('focus', handleFocus);
  }, []);

  const checkAuth = async () => {
    const authenticated = checkIsAuthenticated();
    setIsAuthenticated(authenticated);
    
    // Make sure API client has the token or clears it
    if (authenticated) {
      const token = getStoredToken();
      if (token) {
        apiClient.setToken(token);
        
        // Get username from auth status (only if we don't have it yet and backend is available)
        // Skip if backend is known to be unavailable to avoid repeated failed requests
        if (!username && !usernameFetchAttemptedRef.current && !backendUnavailableRef.current) {
          usernameFetchAttemptedRef.current = true;
          try {
            const authStatus = await apiClient.getAuthStatus();
            if (authStatus.username) {
              setUsername(authStatus.username);
              backendUnavailableRef.current = false; // Backend is working
            }
          } catch (error: any) {
            // Check if this is a connection error
            const isConnectionError = 
              error.code === 'ECONNABORTED' || 
              error.code === 'ECONNRESET' || 
              error.code === 'ECONNREFUSED' ||
              error.message?.includes('socket hang up') ||
              error.message?.includes('timeout') ||
              error.message?.includes('Network Error') ||
              error.response?.status === 503 ||
              error.response?.status === 502;
            
            if (isConnectionError) {
              // Mark backend as unavailable to avoid repeated requests
              backendUnavailableRef.current = true;
              // Don't reset attempt flag - keep it true so we don't retry until explicitly reset
              // This prevents repeated failed requests when backend is down
            } else {
              // Non-connection error - log it and reset attempt flag
              console.error('Failed to get username:', error);
              usernameFetchAttemptedRef.current = false;
            }
          }
        }
      } else {
        // Token missing but authenticated flag is true - clear state
        setIsAuthenticated(false);
        if (username) {
          setUsername(null);
        }
        usernameFetchAttemptedRef.current = false;
        backendUnavailableRef.current = false;
      }
    } else {
      // Not authenticated - clear username and reset flags
      apiClient.setToken(null);
      if (username) {
        setUsername(null);
      }
      usernameFetchAttemptedRef.current = false;
      // Keep backendUnavailableRef as is - it's useful information even when not authenticated
    }
  };

  const handleSendMessage = async (message: string, overrideLanguage?: Language, overrideModel?: string, overrideRepoId?: string, shouldCreatePR?: boolean) => {
    if (!isAuthenticated) {
      alert('Please sign in with HuggingFace first! Click the "Sign in with Hugging Face" button in the header.');
      return;
    }

    // Hide landing page and show full UI when first message is sent
    if (showLandingPage) {
      setShowLandingPage(false);
    }

    // Use override values if provided, otherwise use state
    const language = overrideLanguage || selectedLanguage;
    const model = overrideModel || selectedModel;

    // Update state if override values provided
    if (overrideLanguage) {
      setSelectedLanguage(overrideLanguage);
    }
    if (overrideModel) {
      setSelectedModel(overrideModel);
    }

    // If there's existing code, include it in the message context for modifications
    let enhancedMessage = message;
    const hasRealCode = generatedCode && 
                        generatedCode.length > 50 && 
                        !generatedCode.includes('Your generated code will appear here');
    
    if (hasRealCode) {
      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}`;
    }

    // Add user message (show original message to user, but send enhanced to API)
    const userMessage: Message = {
      role: 'user',
      content: message,
      timestamp: new Date().toISOString(),
    };
    setMessages((prev) => [...prev, userMessage]);
    setIsGenerating(true);
    
    // Clear previous code to show streaming from start
    setGeneratedCode('');

    // Prepare request with enhanced query that includes current code
    // Use overrideRepoId if provided (from import/duplicate), otherwise use currentRepoId from state
    const effectiveRepoId = overrideRepoId || currentRepoId || undefined;
    
    console.log('[SendMessage] ========== GENERATION REQUEST ==========');
    console.log('[SendMessage] overrideRepoId:', overrideRepoId);
    console.log('[SendMessage] currentRepoId:', currentRepoId);
    console.log('[SendMessage] effectiveRepoId (will use):', effectiveRepoId);
    console.log('[SendMessage] ==========================================');
    
    const request: CodeGenerationRequest = {
      query: enhancedMessage,
      language: language,
      model_id: model,
      provider: 'auto',
      history: messages.map((m) => [m.role, m.content]),
      agent_mode: false,
      existing_repo_id: effectiveRepoId,  // Pass duplicated/imported space ID for auto-deploy
      skip_auto_deploy: !!shouldCreatePR, // Skip auto-deploy if creating PR
    };

    const assistantMessage: Message = {
      role: 'assistant',
      content: '⏳ Generating code...',
      timestamp: new Date().toISOString(),
    };

    // Add placeholder for assistant message
    setMessages((prev) => [...prev, assistantMessage]);

    // Stream the response
    try {
      apiClient.generateCodeStream(
        request,
        // onChunk - Update code editor in real-time with immediate flush
        (chunk: string) => {
          console.log('[Stream] Received chunk:', chunk.substring(0, 50), '... (length:', chunk.length, ')');
          // Use flushSync to force immediate DOM update without React batching
          flushSync(() => {
            setGeneratedCode((prevCode) => {
              const newCode = prevCode + chunk;
              console.log('[Stream] Total code length:', newCode.length);
              return newCode;
            });
          });
        },
        // onComplete
        (code: string) => {
          setGeneratedCode(code);
          setIsGenerating(false);
          
          // Update final message - just show success, not the code
          setMessages((prev) => {
            const newMessages = [...prev];
            newMessages[newMessages.length - 1] = {
              ...assistantMessage,
              content: 'βœ… Code generated successfully! Check the editor β†’',
            };
            return newMessages;
          });
          
          // Check if we need to create a PR (redesign with PR option)
          console.log('[PR] onComplete - Checking pendingPR ref:', pendingPRRef.current);
          console.log('[PR] onComplete - Checking pendingPR state:', pendingPR);
          const prInfo = pendingPRRef.current;
          if (prInfo) {
            console.log('[PR] Creating pull request for:', prInfo.repoId);
            createPullRequestAfterGeneration(prInfo.repoId, code, prInfo.language);
            setPendingPR(null); // Clear state
            pendingPRRef.current = null; // Clear ref
          } else {
            console.log('[PR] No pending PR - skipping PR creation');
          }
        },
        // onError
        (error: string) => {
          setIsGenerating(false);
          setMessages((prev) => {
            const newMessages = [...prev];
            newMessages[newMessages.length - 1] = {
              ...assistantMessage,
              content: `❌ Error: ${error}`,
            };
            return newMessages;
          });
        },
        // onDeploying
        (message: string) => {
          console.log('[Deploy] Deployment started:', message);
          // Update message to show deployment in progress
          setMessages((prev) => {
            const newMessages = [...prev];
            newMessages[newMessages.length - 1] = {
              ...assistantMessage,
              content: `βœ… Code generated successfully!\n\n${message}`,
            };
            return newMessages;
          });
        },
        // onDeployed
        (message: string, spaceUrl: string) => {
          console.log('[Deploy] Deployment successful:', spaceUrl);
          
          // Extract repo_id from space URL
          const match = spaceUrl.match(/huggingface\.co\/spaces\/([^\/\s\)]+\/[^\/\s\)]+)/);
          if (match) {
            setCurrentRepoId(match[1]);
          }
          
          // Update message with deployment success - use backend message format for history tracking
          setMessages((prev) => {
            const newMessages = [...prev];
            newMessages[newMessages.length - 1] = {
              ...assistantMessage,
              content: `βœ… Code generated successfully!\n\n${message}`,
            };
            return newMessages;
          });
          
          // Open the space URL in a new tab
          window.open(spaceUrl, '_blank');
        },
        // onDeployError
        (message: string) => {
          console.log('[Deploy] Deployment error:', message);
          // Update message to show deployment failed (but code generation succeeded)
          setMessages((prev) => {
            const newMessages = [...prev];
            newMessages[newMessages.length - 1] = {
              ...assistantMessage,
              content: `βœ… Code generated successfully!\n\n${message}\n\nYou can still use the "Publish" button to deploy manually.`,
            };
            return newMessages;
          });
        }
      );
    } catch (error) {
      setIsGenerating(false);
      setMessages((prev) => {
        const newMessages = [...prev];
        newMessages[newMessages.length - 1] = {
          ...assistantMessage,
          content: `❌ Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
        };
        return newMessages;
      });
    }
  };

  const createPullRequestAfterGeneration = async (repoId: string, code: string, language: Language) => {
    try {
      console.log('[PR] Creating PR on:', repoId);
      
      // Update message to show PR creation in progress
      setMessages((prev) => {
        const newMessages = [...prev];
        newMessages[newMessages.length - 1] = {
          ...newMessages[newMessages.length - 1],
          content: 'βœ… Code generated successfully!\n\nπŸ”„ Creating Pull Request...',
        };
        return newMessages;
      });
      
      const prResult = await apiClient.createPullRequest(
        repoId,
        code,
        language,
        '🎨 Redesign from AnyCoder',
        undefined
      );
      
      if (prResult.success && prResult.pr_url) {
        console.log('[PR] Pull Request created:', prResult.pr_url);
        
        // Update message with PR link
        setMessages((prev) => {
          const newMessages = [...prev];
          newMessages[newMessages.length - 1] = {
            ...newMessages[newMessages.length - 1],
            content: `βœ… Code generated successfully!\n\nβœ… Pull Request created! [View PR](${prResult.pr_url})`,
          };
          return newMessages;
        });
        
        // Open PR in new tab
        window.open(prResult.pr_url, '_blank');
      } else {
        throw new Error(prResult.message || 'Failed to create Pull Request');
      }
    } catch (error: any) {
      console.error('[PR] Failed to create Pull Request:', error);
      
      // Update message with error
      setMessages((prev) => {
        const newMessages = [...prev];
        newMessages[newMessages.length - 1] = {
          ...newMessages[newMessages.length - 1],
          content: `βœ… Code generated successfully!\n\n❌ Failed to create Pull Request: ${error.message || 'Unknown error'}`,
        };
        return newMessages;
      });
    }
  };

  const handleDeploy = async () => {
    console.log('[Deploy] 🎬 handleDeploy called');
    console.log('[Deploy] generatedCode exists?', !!generatedCode);
    console.log('[Deploy] generatedCode length:', generatedCode?.length);
    console.log('[Deploy] generatedCode preview:', generatedCode?.substring(0, 200));
    
    if (!generatedCode) {
      alert('No code to publish! Generate some code first.');
      return;
    }

    // Get current username (fetch if not loaded)
    let currentUsername = username;
    if (!currentUsername) {
      console.log('[Deploy] Username not in state, fetching from auth...');
      try {
        const authStatus = await apiClient.getAuthStatus();
        if (authStatus.username) {
          currentUsername = authStatus.username;
          setUsername(authStatus.username);
          console.log('[Deploy] Fetched username:', currentUsername);
        }
      } catch (e) {
        console.error('[Deploy] Could not get username:', e);
        // Don't fail - let backend handle auth
      }
    }

    // SAME LOGIC AS GRADIO VERSION: Parse message history to find existing space
    let existingSpace: string | null = null;
    
    // Look for previous deployment or imported space in history
    console.log('[Deploy] ========== DEBUG START ==========');
    console.log('[Deploy] Total messages in history:', messages.length);
    console.log('[Deploy] Current username:', currentUsername);
    console.log('[Deploy] Auth status:', isAuthenticated ? 'authenticated' : 'not authenticated');
    console.log('[Deploy] Messages:', JSON.stringify(messages, null, 2));
    
    if (messages.length > 0 && currentUsername) {
      console.log('[Deploy] Scanning message history FORWARD (oldest first) - MATCHING GRADIO LOGIC...');
      console.log('[Deploy] Total messages to scan:', messages.length);
      
      // EXACT GRADIO LOGIC: Scan forward (oldest first) and stop at first match
      // Gradio: for user_msg, assistant_msg in history:
      for (let i = 0; i < messages.length; i++) {
        const msg = messages[i];
        console.log(`[Deploy] Checking message ${i}:`, {
          role: msg.role,
          contentPreview: msg.content.substring(0, 100)
        });
        
        // Check assistant messages for deployment confirmations
        if (msg.role === 'assistant') {
          // Check for "βœ… Deployed!" message
          if (msg.content.includes('βœ… Deployed!')) {
            const match = msg.content.match(/huggingface\.co\/spaces\/([^\/\s\)]+\/[^\/\s\)]+)/);
            if (match) {
              existingSpace = match[1];
              console.log('[Deploy] βœ… Found "βœ… Deployed!" - existing_space:', existingSpace);
              break;
            }
          }
          // Check for "βœ… Updated!" message
          else if (msg.content.includes('βœ… Updated!')) {
            const match = msg.content.match(/huggingface\.co\/spaces\/([^\/\s\)]+\/[^\/\s\)]+)/);
            if (match) {
              existingSpace = match[1];
              console.log('[Deploy] βœ… Found "βœ… Updated!" - existing_space:', existingSpace);
              break;
            }
          }
        }
        // Check user messages for imports
        else if (msg.role === 'user' && msg.content.startsWith('Imported Space from')) {
          console.log('[Deploy] 🎯 Found "Imported Space from" message');
          const match = msg.content.match(/huggingface\.co\/spaces\/([^\/\s\)]+\/[^\/\s\)]+)/);
          if (match) {
            const importedSpace = match[1];
            console.log('[Deploy] Extracted imported space:', importedSpace);
            console.log('[Deploy] Checking ownership - user:', currentUsername, 'space:', importedSpace);
            
            // Only use if user owns it (EXACT GRADIO LOGIC)
            if (importedSpace.startsWith(`${currentUsername}/`)) {
              existingSpace = importedSpace;
              console.log('[Deploy] βœ…βœ…βœ… USER OWNS - Will update:', existingSpace);
              break;
            } else {
              console.log('[Deploy] ⚠️ User does NOT own - will create new space');
              // existing_space remains None (create new deployment)
            }
          }
        }
      }
      
      console.log('[Deploy] Final existingSpace value:', existingSpace);
    } else {
      console.log('[Deploy] Skipping scan - no messages or no username');
      console.log('[Deploy] Messages length:', messages.length);
      console.log('[Deploy] Username:', currentUsername);
    }
    console.log('[Deploy] ========== DEBUG END ==========');

    // TEMPORARY DEBUG: Show what will be sent
    console.log('[Deploy] πŸš€ ABOUT TO DEPLOY:');
    console.log('[Deploy] - Language:', selectedLanguage);
    console.log('[Deploy] - existing_repo_id:', existingSpace || 'None (new deployment)');
    console.log('[Deploy] - Username:', currentUsername);

    // Auto-generate space name (never prompt user)
    let spaceName = undefined;  // undefined = backend will auto-generate

    try {
      console.log('[Deploy] ========== DEPLOY START (Gradio-style history parsing) ==========');
      console.log('[Deploy] Username:', currentUsername);
      console.log('[Deploy] Existing space from history:', existingSpace);
      console.log('[Deploy] Will create new space?', !existingSpace);
      console.log('[Deploy] Messages count:', messages.length);
      console.log('[Deploy] Messages (first 3):', messages.slice(0, 3).map(m => ({ role: m.role, content: m.content.substring(0, 100) })));
      
      // CRITICAL DEBUG: Check what we're actually sending
      const historyToSend = messages.map(msg => ({ role: msg.role, content: msg.content }));
      console.log('[Deploy] History to send (length):', historyToSend.length);
      console.log('[Deploy] History to send (first 2):', historyToSend.slice(0, 2));
      console.log('[Deploy] =================================================================');
      
      // Build deploy request, omitting undefined fields
      const deployRequest: any = {
        code: generatedCode,
        language: selectedLanguage,
        history: historyToSend  // Use the variable we just logged
      };
      
      // Only include optional fields if they have values
      if (spaceName) {
        deployRequest.space_name = spaceName;
      }
      if (existingSpace) {
        deployRequest.existing_repo_id = existingSpace;
        deployRequest.commit_message = 'Update via AnyCoder';
      }
      
      console.log('[Deploy] πŸš€ Sending to backend:', {
        existing_repo_id: deployRequest.existing_repo_id,
        space_name: deployRequest.space_name,
        language: deployRequest.language,
        has_code: !!deployRequest.code,
        code_length: deployRequest.code?.length,
        history_length: deployRequest.history?.length
      });
      console.log('[Deploy] Full request object:', JSON.stringify(deployRequest, null, 2).substring(0, 500));
      
      const response = await apiClient.deploy(deployRequest);
      console.log('[Deploy] βœ… Response received:', response);

      if (response.success) {
        // Update current repo ID if we got one back
        if (response.repo_id) {
          console.log('[Deploy] Setting currentRepoId to:', response.repo_id);
          setCurrentRepoId(response.repo_id);
        } else if (response.space_url) {
          // Extract repo_id from space_url as fallback
          const match = response.space_url.match(/huggingface\.co\/spaces\/([^\/\s\)]+\/[^\/\s\)]+)/);
          if (match) {
            console.log('[Deploy] Extracted repo_id from URL:', match[1]);
            setCurrentRepoId(match[1]);
          }
        }
        
        // Add deployment message to chat (EXACT format backend expects)
        const deployMessage: Message = {
          role: 'assistant',
          content: existingSpace 
            ? `βœ… Updated! View your space at: ${response.space_url}` 
            : `βœ… Deployed! View your space at: ${response.space_url}`,
          timestamp: new Date().toISOString(),
        };
        setMessages((prev) => [...prev, deployMessage]);
        
        // Open the space URL in a new tab
        window.open(response.space_url, '_blank');
        
        // Show success message
        const isDev = response.dev_mode;
        const message = isDev 
          ? 'πŸš€ Opening HuggingFace Spaces creation page...\nPlease complete the space setup in the new tab.'
          : existingSpace
            ? `βœ… Updated successfully!\n\nOpening: ${response.space_url}`
            : `βœ… Published successfully!\n\nOpening: ${response.space_url}`;
        alert(message);
      } else {
        alert(`Deployment failed: ${response.message}`);
      }
    } catch (error: any) {
      console.error('[Deploy] Full error object:', error);
      console.error('[Deploy] Error response:', error.response);
      console.error('[Deploy] Error data:', error.response?.data);
      
      const errorMessage = error.response?.data?.detail 
        || error.response?.data?.message 
        || error.message 
        || 'Unknown error';
      
      alert(`Deployment error: ${errorMessage}\n\nCheck console for details.`);
    }
  };

  const handleClear = () => {
    if (confirm('Clear all messages and code?')) {
      setMessages([]);
      setGeneratedCode('');
      setShowLandingPage(true);
      // Clear localStorage to remove import history
      if (typeof window !== 'undefined') {
        localStorage.removeItem('anycoder_messages');
        console.log('[localStorage] Cleared messages from localStorage');
      }
    }
  };

  const handleImport = (code: string, language: Language, importUrl?: string) => {
    console.log('[Import] ========== IMPORT START ==========');
    console.log('[Import] Language:', language);
    console.log('[Import] Import URL:', importUrl);
    console.log('[Import] Current username:', username);
    console.log('[Import] Current repo before import:', currentRepoId);
    
    // Hide landing page when importing
    if (showLandingPage) {
      setShowLandingPage(false);
    }
    
    setGeneratedCode(code);
    setSelectedLanguage(language);
    
    // Extract repo_id from import URL if provided
    if (importUrl) {
      const spaceMatch = importUrl.match(/huggingface\.co\/spaces\/([^\/\s\)]+\/[^\/\s\)]+)/);
      console.log('[Import] Regex match result:', spaceMatch);
      
      if (spaceMatch) {
        const importedRepoId = spaceMatch[1];
        const importedUsername = importedRepoId.split('/')[0];
        
        console.log('[Import] ========================================');
        console.log('[Import] Extracted repo_id:', importedRepoId);
        console.log('[Import] Imported username:', importedUsername);
        console.log('[Import] Logged-in username:', username);
        console.log('[Import] Ownership check:', importedUsername === username);
        console.log('[Import] ========================================');
        
        // Only set as current repo if user owns it
        if (username && importedRepoId.startsWith(`${username}/`)) {
          console.log('[Import] βœ…βœ…βœ… BEFORE setCurrentRepoId - currentRepoId was:', currentRepoId);
          setCurrentRepoId(importedRepoId);
          console.log('[Import] βœ…βœ…βœ… CALLED setCurrentRepoId with:', importedRepoId);
          console.log('[Import] βœ…βœ…βœ… Note: State update is async, currentRepoId will update later');
        } else {
          // User doesn't own the imported space, clear current repo
          setCurrentRepoId(null);
          if (!username) {
            console.log('[Import] ⚠️⚠️⚠️ USERNAME IS NULL - Cannot set repo ownership!');
          } else {
            console.log('[Import] ⚠️ User does not own imported space:', importedRepoId, '(username:', username, ')');
          }
        }
      } else {
        console.log('[Import] ⚠️ Could not extract repo_id from URL:', importUrl);
      }
    } else {
      console.log('[Import] No import URL provided');
    }
    
    console.log('[Import] ========== IMPORT END ==========');
    
    // Add messages that include the imported code so LLM can see it
    const userMessage: Message = {
      role: 'user',
      content: importUrl 
        ? `Imported Space from ${importUrl}`
        : `I imported a ${language} project. Here's the code that was imported.`,
      timestamp: new Date().toISOString(),
    };
    
    const assistantMessage: Message = {
      role: 'assistant',
      content: `βœ… I've loaded your ${language} project. The code is now in the editor. You can ask me to:\n\nβ€’ Modify existing features\nβ€’ Add new functionality\nβ€’ Fix bugs or improve code\nβ€’ Explain how it works\nβ€’ Publish it to HuggingFace Spaces\n\nWhat would you like me to help you with?`,
      timestamp: new Date().toISOString(),
    };
    
    setMessages((prev) => [...prev, userMessage, assistantMessage]);
    
    // Switch to editor view on mobile
    setMobileView('editor');
  };

  // Handle landing page prompt submission
  const handleLandingPageStart = async (prompt: string, language: Language, modelId: string, repoId?: string, shouldCreatePR?: boolean) => {
    // Hide landing page immediately for smooth transition
    setShowLandingPage(false);
    
    // If shouldCreatePR is true, set pending PR state and ref
    if (shouldCreatePR && repoId) {
      console.log('[PR] Setting pending PR for:', repoId);
      const prInfo = { repoId, language };
      setPendingPR(prInfo);
      pendingPRRef.current = prInfo;  // Set ref immediately for synchronous access
    }
    
    // Send the message with the selected language and model
    // Don't pass repoId to handleSendMessage when creating PR (we want to generate code first, then create PR)
    await handleSendMessage(prompt, language, modelId, shouldCreatePR ? undefined : repoId, shouldCreatePR);
  };

  // Resize handlers for chat sidebar (desktop only)
  const startResizingChat = () => {
    if (isDesktop) {
      setIsResizingChat(true);
    }
  };

  const startResizingSettings = () => {
    if (isDesktop) {
      setIsResizingSettings(true);
    }
  };

  // Handle mouse move for resizing (desktop only)
  useEffect(() => {
    const handleMouseMove = (e: MouseEvent) => {
      if (!isDesktop) return; // Skip on mobile
      
      if (isResizingChat) {
        const newWidth = Math.min(Math.max(e.clientX, 250), 600); // Min 250px, max 600px
        setChatSidebarWidth(newWidth);
      }
      if (isResizingSettings) {
        const newWidth = Math.min(Math.max(window.innerWidth - e.clientX, 220), 500); // Min 220px, max 500px
        setSettingsSidebarWidth(newWidth);
      }
    };

    const handleMouseUp = () => {
      if (isResizingChat) {
        setIsResizingChat(false);
        // Save to localStorage
        localStorage.setItem('anycoder_chat_sidebar_width', chatSidebarWidth.toString());
        document.body.classList.remove('resizing');
      }
      if (isResizingSettings) {
        setIsResizingSettings(false);
        // Save to localStorage
        localStorage.setItem('anycoder_settings_sidebar_width', settingsSidebarWidth.toString());
        document.body.classList.remove('resizing');
      }
    };

    if (isResizingChat || isResizingSettings) {
      document.addEventListener('mousemove', handleMouseMove);
      document.addEventListener('mouseup', handleMouseUp);
      // Add resizing class to body for cursor and selection styles
      document.body.classList.add('resizing');
    }

    return () => {
      document.removeEventListener('mousemove', handleMouseMove);
      document.removeEventListener('mouseup', handleMouseUp);
      document.body.classList.remove('resizing');
    };
  }, [isResizingChat, isResizingSettings, chatSidebarWidth, settingsSidebarWidth, isDesktop]);

  // Show landing page if no messages and showLandingPage is true
  if (showLandingPage && messages.length === 0) {
    return (
      <div className="min-h-screen animate-in fade-in duration-300">
        <LandingPage 
          onStart={handleLandingPageStart}
          onImport={handleImport}
          isAuthenticated={isAuthenticated}
          initialLanguage={selectedLanguage}
          initialModel={selectedModel}
          onAuthChange={checkAuth}
          setPendingPR={setPendingPR}
          pendingPRRef={pendingPRRef}
        />
      </div>
    );
  }

  return (
    <div className="h-screen flex flex-col bg-[#000000] animate-in fade-in duration-300">
      <Header />
      
      {/* Apple-style layout - Responsive */}
      <main className="flex-1 flex overflow-hidden relative">
        {/* Left Sidebar - Chat Panel (Hidden on mobile, shown when mobileView='chat') */}
        <div 
          className={`
            ${mobileView === 'chat' ? 'flex' : 'hidden'} md:flex
            w-full
            bg-[#000000] border-r border-[#424245]/30 
            flex-col
            absolute md:relative inset-0 md:inset-auto z-10 md:z-auto
            md:flex-shrink-0
          `}
          style={isDesktop ? { width: `${chatSidebarWidth}px` } : undefined}
        >
          {/* Panel Header */}
          <div className="flex items-center px-4 py-3 bg-[#000000] border-b border-[#424245]/30">
            <span className="text-sm font-medium text-[#f5f5f7]">Chat</span>
          </div>
          
          {/* Chat Panel */}
          <div className="flex-1 overflow-hidden">
            <ChatInterface
              messages={messages}
              onSendMessage={handleSendMessage}
              isGenerating={isGenerating}
              isAuthenticated={isAuthenticated}
            />
          </div>
        </div>

        {/* Resize Handle for Chat Sidebar (Desktop only) */}
        <div 
          className={`hidden md:block resize-handle ${isResizingChat ? 'resizing' : ''}`}
          onMouseDown={startResizingChat}
          title="Drag to resize chat panel"
        />

        {/* Center - Editor Group (Always visible on mobile when mobileView='editor', always visible on desktop) */}
        <div className={`
          ${mobileView === 'editor' ? 'flex' : 'hidden'} md:flex
          flex-1 flex-col bg-[#000000]
          absolute md:relative inset-0 md:inset-auto z-10 md:z-auto
          md:min-w-0 overflow-hidden
          w-full
        `}>
          {/* Tab Bar */}
          <div className="flex items-center px-4 h-10 bg-[#1d1d1f] border-b border-[#424245]/30">
            <div className="flex items-center space-x-2">
              <div className="px-3 py-1 bg-[#2d2d2f] text-sm text-[#f5f5f7] rounded-t-lg font-normal border-t border-x border-[#424245]/50">
                {selectedLanguage === 'html' ? 'app.html' :
                 selectedLanguage === 'gradio' || selectedLanguage === 'streamlit' ? 'app.py' : 
                 selectedLanguage === 'transformers.js' ? 'app.js' :
                 selectedLanguage === 'comfyui' ? 'app.json' :
                 selectedLanguage === 'react' ? 'app.jsx' :
                 `${selectedLanguage}.txt`}
              </div>
            </div>
            <div className="ml-auto flex items-center space-x-3 text-xs text-[#86868b]">
              {isGenerating && (
                <span className="flex items-center space-x-1.5">
                  <div className="w-1.5 h-1.5 bg-white rounded-full animate-pulse"></div>
                  <span>Generating...</span>
                </span>
              )}
              <span className="font-medium">{selectedLanguage.toUpperCase()}</span>
            </div>
          </div>
          
          {/* Editor */}
          <div className="flex-1">
            <CodeEditor
              code={generatedCode || '// Your generated code will appear here...\n// Select a model and start chatting to generate code'}
              language={selectedLanguage}
              onChange={setGeneratedCode}
              readOnly={isGenerating}
            />
          </div>
        </div>

        {/* Resize Handle for Settings Sidebar (Desktop only) */}
        <div 
          className={`hidden md:block resize-handle ${isResizingSettings ? 'resizing' : ''}`}
          onMouseDown={startResizingSettings}
          title="Drag to resize settings panel"
        />

        {/* Right Sidebar - Configuration Panel (Hidden on mobile, shown when mobileView='settings') */}
        <div 
          className={`
            ${mobileView === 'settings' ? 'flex' : 'hidden'} md:flex
            w-full
            bg-[#000000] border-l border-[#424245]/30 
            overflow-y-auto
            absolute md:relative inset-0 md:inset-auto z-10 md:z-auto
            flex-col
            md:flex-shrink-0
          `}
          style={isDesktop ? { width: `${settingsSidebarWidth}px` } : undefined}
        >
          <ControlPanel
            selectedLanguage={selectedLanguage}
            selectedModel={selectedModel}
            onLanguageChange={setSelectedLanguage}
            onModelChange={setSelectedModel}
            onClear={handleClear}
            onImport={handleImport}
            isGenerating={isGenerating}
          />
        </div>
      </main>

      {/* Mobile Bottom Navigation (visible only on mobile) */}
      <nav className="md:hidden bg-[#000000]/95 backdrop-blur-xl border-t border-[#424245]/20 flex items-center justify-around h-14 px-2 safe-area-bottom">
        <button
          onClick={() => setMobileView('chat')}
          className={`flex flex-col items-center justify-center flex-1 py-1.5 transition-all ${
            mobileView === 'chat' 
              ? 'text-white' 
              : 'text-[#86868b]'
          }`}
        >
          <svg className="w-5 h-5 mb-0.5" fill="none" stroke="currentColor" viewBox="0 0 24 24" strokeWidth={2}>
            <path strokeLinecap="round" strokeLinejoin="round" d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z" />
          </svg>
          <span className="text-[10px]">Chat</span>
        </button>
        
        <button
          onClick={() => setMobileView('editor')}
          className={`flex flex-col items-center justify-center flex-1 py-1.5 transition-all ${
            mobileView === 'editor' 
              ? 'text-white' 
              : 'text-[#86868b]'
          }`}
        >
          <svg className="w-5 h-5 mb-0.5" fill="none" stroke="currentColor" viewBox="0 0 24 24" strokeWidth={2}>
            <path strokeLinecap="round" strokeLinejoin="round" d="M10 20l4-16m4 4l4 4-4 4M6 16l-4-4 4-4" />
          </svg>
          <span className="text-[10px]">Code</span>
        </button>
        
        <button
          onClick={() => setMobileView('settings')}
          className={`flex flex-col items-center justify-center flex-1 py-1.5 transition-all ${
            mobileView === 'settings' 
              ? 'text-white' 
              : 'text-[#86868b]'
          }`}
        >
          <svg className="w-5 h-5 mb-0.5" fill="none" stroke="currentColor" viewBox="0 0 24 24" strokeWidth={2}>
            <path strokeLinecap="round" strokeLinejoin="round" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z" />
            <path strokeLinecap="round" strokeLinejoin="round" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
          </svg>
          <span className="text-[10px]">Settings</span>
        </button>
      </nav>

      {/* Status Bar - Apple style (hidden on mobile) */}
      <footer className="hidden md:flex h-6 bg-[#000000] border-t border-[#424245]/20 text-[#86868b] text-[11px] items-center px-4 justify-between">
        <div className="flex items-center space-x-4">
          <span>AnyCoder</span>
          <span className="flex items-center gap-1.5">
            {isAuthenticated ? (
              <>
                <span className="w-1.5 h-1.5 bg-[#30d158] rounded-full"></span>
                <span>Connected</span>
              </>
            ) : (
              <>
                <span className="w-1.5 h-1.5 bg-[#ff9f0a] rounded-full"></span>
                <span>Not authenticated</span>
              </>
            )}
          </span>
        </div>
        <div className="flex items-center space-x-4">
          <span>{messages.length} messages</span>
        </div>
      </footer>
    </div>
  );
}