File size: 44,278 Bytes
ced61cd
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
"""

Personal Diary Chatbot Interface - Simplified Version



A streamlined Streamlit-based web application for diary management and AI chat.

"""
import os
import sys
import re
import hashlib
import streamlit as st
import random
import time
import subprocess
from datetime import datetime
from typing import Generator, List
from backend.get_post_v3 import submit_text_to_database, load_entries_from_database, delete_diary_entry
from auth_ui import AuthUI

# Voice Input Dependencies
try:
    from streamlit_webrtc import webrtc_streamer, WebRtcMode, RTCConfiguration
    import av
    import numpy as np
    import google.generativeai as genai
    import tempfile
    import threading
    import queue
    import concurrent.futures
    VOICE_AVAILABLE = True
except ImportError as e:
    print(f"Voice input dependencies not available: {e}")
    VOICE_AVAILABLE = False

# Load environment variables
from dotenv import load_dotenv
load_dotenv()

# Add parent directory to path for RAG system import
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))

# Import RAG client
try:
    from rag_client import RAGServiceClient
    rag_client = RAGServiceClient()
    RAG_AVAILABLE = True
    print("βœ… RAG client imported successfully")
except ImportError as e:
    print(f"Warning: RAG client not available: {e}")
    rag_client = None
    RAG_AVAILABLE = False

# ========================================
# VOICE INPUT FUNCTIONS
# ========================================

def get_user_audio_directory(user_id: int) -> str:
    """Get user-specific audio directory path."""
    # Get project root directory (go up from src/streamlit_app/)
    current_dir = os.path.dirname(os.path.abspath(__file__))
    project_root = os.path.dirname(os.path.dirname(current_dir))
    audio_dir = os.path.join(project_root, "user_audio", f"user_{user_id}_audio")
    os.makedirs(audio_dir, exist_ok=True)
    return audio_dir

def transcribe_audio_with_gemini_live(audio_data: bytes, user_id: int) -> str:
    """Transcribe audio using Gemini API."""
    try:
        # Get API key
        api_key = os.getenv("GOOGLE_API_KEY")
        if not api_key:
            return "❌ Google API key not configured"
        
        # Configure Gemini
        genai.configure(api_key=api_key)
        
        # Save audio temporarily
        audio_dir = get_user_audio_directory(user_id)
        temp_audio_path = os.path.join(audio_dir, f"temp_audio_{int(time.time())}.wav")
        try:
            with open(temp_audio_path, 'wb') as f:
                f.write(audio_data)
            
            # Upload audio file to Gemini
            audio_file = genai.upload_file(path=temp_audio_path, mime_type="audio/wav")
            
            # Use Gemini model for transcription
            model = genai.GenerativeModel("gemini-2.5-flash-lite")
            prompt = """Convert speech to text. Please transcribe this audio recording accurately.

            

Instructions:

- Listen to the audio and convert the spoken words to text

- Maintain proper grammar and punctuation

- Return only the transcribed text, no additional commentary

- If you cannot understand parts of the audio, indicate with [unclear]



Transcription:"""

            response = model.generate_content([prompt, audio_file])

            if response and response.text:
                # Clean up the uploaded file
                try:
                    genai.delete_file(audio_file.name)
                except Exception:
                    pass

                return response.text.strip()
            else:
                return "❌ No transcription received"
        finally:
            # Clean up temporary file
            if os.path.exists(temp_audio_path):
                try:
                    os.remove(temp_audio_path)
                except Exception as e:
                    print(f"Warning: Could not delete temp audio file: {e}")
    except PermissionError:
        return "⚠️ Vui lΓ²ng cαΊ₯p quyền truy cαΊ­p microphone"
    except Exception as e:
        print(f"Transcription error: {e}")
        return f"❌ Transcription failed: {str(e)}"

class AudioProcessor:
    """Audio processor for real-time audio capture."""
    
    def __init__(self):
        self.audio_frames = queue.Queue()
        self.is_recording = False
    
    def audio_frame_callback(self, frame):
        """Callback for processing audio frames."""
        if self.is_recording:
            audio_array = frame.to_ndarray()
            self.audio_frames.put(audio_array)
        return frame
    
    def start_recording(self):
        """Start recording audio."""
        self.is_recording = True
        self.audio_frames = queue.Queue()
    
    def stop_recording(self):
        """Stop recording and return audio data."""
        self.is_recording = False
        
        # Collect all audio frames
        frames = []
        while not self.audio_frames.empty():
            try:
                frame = self.audio_frames.get_nowait()
                frames.append(frame)
            except queue.Empty:
                break
        
        if not frames:
            return None
        
        # Concatenate frames and ensure proper format
        audio_data = np.concatenate(frames, axis=0)
        
        # Ensure audio is mono (single channel)
        if audio_data.ndim > 1:
            audio_data = np.mean(audio_data, axis=1)
        
        # Normalize audio data to prevent distortion
        if np.max(np.abs(audio_data)) > 0:
            audio_data = audio_data / np.max(np.abs(audio_data)) * 0.8
        
        # Convert to 16-bit PCM format
        audio_bytes = (audio_data * 32767).astype(np.int16).tobytes()
        
        return audio_bytes

# ========================================
# HELPER FUNCTIONS
# ========================================

def extract_title_from_content(content: str) -> str:
    """Extract title from content string."""
    if not content:
        return "Untitled"
    lines = content.split('\n')
    for line in lines:
        if line.startswith('Title: '):
            return line[7:].strip()
    return "Untitled"

def extract_content_from_entry(content: str) -> str:
    """Extract actual content from full content string."""
    if not content:
        return ""
    lines = content.split('\n')
    content_start = False
    result_lines = []
    
    for line in lines:
        if line.startswith('Content: '):
            content_start = True
            result_lines.append(line[9:])
        elif content_start:
            result_lines.append(line)
    
    return '\n'.join(result_lines).strip()

def extract_tags_from_content(content: str) -> List[str]:
    """Extract #tags from content string."""
    if not content:
        return []
    tag_pattern = r'#(\w+(?:[_-]\w+)*)'
    matches = re.findall(tag_pattern, content, re.IGNORECASE)
    return list(set([tag.lower() for tag in matches]))

def parse_tags_input(tags_input: str) -> List[str]:
    """Parse comma-separated tags input."""
    if not tags_input:
        return []
    tags = []
    for tag in tags_input.split(','):
        tag = tag.strip()
        if tag.startswith('#'):
            tag = tag[1:]
        if tag:
            tags.append(tag.lower())
    return list(set(tags))

def generate_tag_color(tag: str) -> str:
    """Generate consistent color for a tag."""
    hash_obj = hashlib.md5(tag.encode())
    hash_hex = hash_obj.hexdigest()
    r = max(60, min(200, int(hash_hex[0:2], 16)))
    g = max(60, min(200, int(hash_hex[2:4], 16)))
    b = max(60, min(200, int(hash_hex[4:6], 16)))
    return f"rgb({r}, {g}, {b})"

def render_tags(tags: List[str]) -> str:
    """Render tags as colored HTML badges."""
    if not tags:
        return ""
    tag_html = []
    for tag in tags:
        color = generate_tag_color(tag)
        tag_html.append(f'<span style="background-color: {color}; color: white; padding: 2px 8px; border-radius: 12px; font-size: 0.8em; margin: 2px; display: inline-block; font-weight: bold;">#{tag}</span>')
    return "".join(tag_html)

def check_rag_service():
    """Check if RAG service is running."""
    if rag_client:
        return rag_client.health_check()
    return False

def check_ai_availability_detailed(user_id: int):
    """Check detailed AI availability status."""
    if not rag_client:
        return {"overall_status": "error", "error": "RAG client not initialized"}
    
    return rag_client.check_ai_availability(user_id)

def fix_ai_availability(user_id: int):
    """Attempt to fix AI availability issues."""
    if not rag_client:
        return {"status": "error", "error": "RAG client not initialized"}
    
    return rag_client.fix_ai_availability(user_id)

def render_ai_status_widget(user_id: int):
    """Render AI status widget with detailed diagnostics and fix options."""
    st.markdown("### πŸ€– AI Assistant Status")
    
    status = check_ai_availability_detailed(user_id)
    overall_status = status.get("overall_status", "unknown")
    
    # Overall status display
    if overall_status == "available":
        st.success("βœ… AI Assistant is fully available!")
    elif overall_status == "partial":
        st.warning("⚠️ AI Assistant is partially available")
    elif overall_status == "unavailable":
        st.error("❌ AI Assistant is unavailable")
    elif overall_status == "not_configured":
        st.warning("⚠️ AI Assistant needs configuration")
    elif overall_status == "needs_indexing":
        st.info("ℹ️ AI Assistant needs initial indexing")
    elif overall_status == "empty_database":
        st.warning("⚠️ AI Assistant has no documents to search")
    elif overall_status == "checking":
        st.info("πŸ”„ Checking AI Assistant status...")
    elif overall_status == "error":
        error_msg = status.get('error', 'Unknown error')
        st.error(f"❌ AI Assistant error: {error_msg}")
    else:
        st.warning(f"⚠️ Unknown AI status: {overall_status}")
        if 'error' in status:
            st.error(f"Details: {status.get('error', 'No details available')}")

def initialize_rag_system():
    """Initialize RAG system using service."""
    current_user_id = getattr(st.session_state, 'current_user_id', 1)
    
    try:
        if not check_rag_service():
            st.error("❌ RAG service is not running. Please start: `python start_rag_service.py`")
            st.session_state.rag_system_status = "service_unavailable"
            return False
        
        with st.spinner("πŸ€– Initializing AI Assistant..."):
            # Get user status
            status = rag_client.get_user_status(current_user_id)
            
            if status.get("status") == "not_indexed":
                st.info("πŸ”„ Creating search index from your diary entries...")
                index_result = rag_client.index_user_data(current_user_id, clear_existing=True)
                
                if index_result.get("status") == "success":
                    st.success(f"βœ… Indexed {index_result.get('documents_processed', 0)} documents")
                    st.session_state.rag_system_status = "initialized"
                    return True
                else:
                    st.error(f"❌ Indexing failed: {index_result.get('error', 'Unknown error')}")
                    st.session_state.rag_system_status = "error"
                    return False
            
            elif status.get("status") == "ready":
                st.success(f"βœ… AI Assistant ready with {status.get('document_count', 0)} documents!")
                st.session_state.rag_system_status = "initialized"
                return True
            
            elif status.get("status") == "error":
                st.error(f"❌ RAG system error: {status.get('error', 'Unknown error')}")
                st.session_state.rag_system_status = "error"
                return False
            
    except Exception as e:
        st.error(f"❌ Cannot initialize AI Assistant: {str(e)}")
        st.session_state.rag_system_status = "error"
        return False

def response_generator(user_query: str) -> Generator[str, None, None]:
    """Generate responses using RAG service."""
    try:
        current_user_id = getattr(st.session_state, 'current_user_id', 1)
        
        if not check_rag_service():
            response = "❌ RAG service is not available. Please start the service first."
        else:
            # Query RAG service
            chat_history = st.session_state.get('messages', [])
            fast_mode = st.session_state.get('fast_mode', False)
            
            result = rag_client.query_rag(
                user_id=current_user_id,
                query=user_query,
                fast_mode=fast_mode,
                chat_history=chat_history
            )
            
            if result.get("status") == "error":
                response = f"❌ Error: {result.get('error', 'Unknown error')}"
            else:
                response = result.get("response", "No response generated")
                # Show processing time in sidebar
                processing_time = result.get("processing_time", 0)
                st.sidebar.success(f"βœ… Response time: {processing_time:.2f}s")
        
    except Exception as e:
        response = f"❌ Error: {str(e)}"
    
    # Stream response
    words = response.split()
    delay = 0.01 if st.session_state.get('fast_mode', False) else 0.03
    
    for word in words:
        yield word + " "
        time.sleep(delay)

def run_auto_sync(user_id: int) -> bool:
    """Auto sync using RAG service after saving new entry."""
    try:
        if not check_rag_service():
            st.warning("⚠️ RAG service not available - entry saved but not indexed")
            return False
        
        # Use the new auto-index endpoint
        result = rag_client.auto_index_new_entry(user_id)
        
        status = result.get("status")
        
        if status == "initial_index_created":
            documents_processed = result.get('documents_processed', 0)
            st.success(f"βœ… Created search index with {documents_processed} documents!")
            return True
        elif status == "incremental_update_success":
            documents_added = result.get('documents_added', 0)
            if documents_added > 0:
                st.success(f"πŸ”„ Updated search index (+{documents_added} documents)")
            else:
                st.info("ℹ️ Search index is up to date")
            return True
        elif status == "full_rebuild_success":
            documents_processed = result.get('documents_processed', 0)
            st.success(f"πŸ”„ Rebuilt search index with {documents_processed} documents")
            return True
        elif status == "skipped":
            reason = result.get('reason', 'Unknown reason')
            st.info(f"ℹ️ Indexing skipped: {reason}")
            return False
        elif status == "failed":
            error = result.get('error', 'Unknown error')
            st.warning(f"⚠️ Indexing failed: {error}")
            return False
        elif status == "error":
            error = result.get('error', 'Unknown error')
            st.error(f"❌ Indexing error: {error}")
            return False
        else:
            st.warning(f"⚠️ Unknown indexing status: {status}")
            return False
            
    except Exception as e:
        st.error(f"❌ Auto-sync error: {e}")
        return False

def initialize_session_state() -> None:
    """Initialize session state variables."""
    if "messages" not in st.session_state:
        st.session_state.messages = []
    
    if "diary_entries" not in st.session_state:
        user_id = getattr(st.session_state, 'current_user_id', 1)
        try:
            st.session_state.diary_entries = load_entries_from_database(user_id)
        except Exception as e:
            st.error(f"Error loading diary entries: {e}")
            st.session_state.diary_entries = []
    
    if "show_form" not in st.session_state:
        st.session_state.show_form = False
    
    if "rag_system" not in st.session_state:
        st.session_state.rag_system = None
        st.session_state.rag_system_status = "not_initialized"
        
        if RAG_AVAILABLE and os.getenv("GOOGLE_API_KEY"):
            st.session_state.rag_system_status = "ready_to_initialize"

def display_chat_history() -> None:
    """Display chat history."""
    for message in st.session_state.messages:
        with st.chat_message(message["role"]):
            st.markdown(message["content"])

def handle_chat_input() -> None:
    """Handle new chat input."""
    if prompt := st.chat_input("Ask me about your diary..."):
        st.session_state.messages.append({"role": "user", "content": prompt})
        
        with st.chat_message("user"):
            st.markdown(prompt)
        
        with st.chat_message("assistant"):
            response = st.write_stream(response_generator(prompt))
        
        st.session_state.messages.append({"role": "assistant", "content": response})

def handle_entry_action(prompt):
    """Handle entry action prompts - generate full AI response."""
    # Add user message
    st.session_state.messages.append({"role": "user", "content": prompt})
    
    # Generate AI response immediately
    try:
        response = ""
        current_user_id = getattr(st.session_state, 'current_user_id', 1)
        
        if not check_rag_service():
            response = "❌ RAG service is not available. Please start the service first."
        else:
            # Query RAG service
            chat_history = st.session_state.get('messages', [])[:-1]  # Exclude the current message
            fast_mode = st.session_state.get('fast_mode', False)
            
            result = rag_client.query_rag(
                user_id=current_user_id,
                query=prompt,
                fast_mode=fast_mode,
                chat_history=chat_history
            )
            
            if result.get("status") == "error":
                response = f"❌ Error: {result.get('error', 'Unknown error')}"
            else:
                response = result.get("response", "No response generated")
        
        # Add AI response to messages
        st.session_state.messages.append({"role": "assistant", "content": response})
        
    except Exception as e:
        response = f"❌ Error generating response: {str(e)}"
        st.session_state.messages.append({"role": "assistant", "content": response})
    
    # Close the menu and rerun to show the conversation
    st.session_state.show_entry_actions = False
    st.rerun()

def check_and_sync_entries():
    """Check and sync entries with RAG system."""
    current_user_id = getattr(st.session_state, 'current_user_id', 1)
    
    try:
        if not check_rag_service():
            st.sidebar.error("❌ RAG service offline")
            return
        
        with st.sidebar.spinner("πŸ”„ Checking sync status..."):
            # Get current status
            status = rag_client.get_user_status(current_user_id)
            doc_count = status.get("document_count", 0)
            
            # Count actual diary entries
            actual_count = len(st.session_state.diary_entries)
            
            if doc_count != actual_count:
                st.sidebar.warning(f"⚠️ Sync issue: {doc_count} indexed vs {actual_count} entries")
                
                if st.sidebar.button("πŸ”„ Fix Sync", key="fix_sync_btn"):
                    with st.sidebar.spinner("πŸ”„ Re-syncing..."):
                        result = rag_client.index_user_data(current_user_id, clear_existing=True)
                        if result.get("status") == "success":
                            st.sidebar.success(f"βœ… Synced {result.get('documents_processed', 0)} documents")
                        else:
                            st.sidebar.error("❌ Sync failed")
            else:
                st.sidebar.success(f"βœ… Sync OK: {doc_count} documents")
                
    except Exception as e:
        st.sidebar.error(f"❌ Sync check error: {str(e)}")

def render_sidebar() -> str:
    """Render sidebar with diary list and controls."""
    st.sidebar.header("πŸ“– Diary List")
    
    # Tag filter
    all_tags = set()
    for entry in st.session_state.diary_entries:
        entry_tags = entry.get('tags', '')
        if entry_tags:
            tags = [tag.strip() for tag in entry_tags.split(',') if tag.strip()]
            all_tags.update(tags)
    
    selected_tag_filter = "All"
    if all_tags:
        selected_tag_filter = st.sidebar.selectbox(
            "Filter by tag:",
            options=["All"] + sorted(list(all_tags)),
            key="tag_filter"
        )
    
    # Filter entries
    filtered_entries = st.session_state.diary_entries
    if selected_tag_filter != "All":
        filtered_entries = [
            entry for entry in st.session_state.diary_entries
            if selected_tag_filter in entry.get('tags', '').split(',')
        ]
    
    st.sidebar.markdown("---")
    
    # Add entry button - Always show this
    if st.sidebar.button("βž• Add New Entry"):
        st.session_state.show_form = not st.session_state.show_form
        st.rerun()
    
    # Show entry list only if there are entries
    if not filtered_entries:
        st.sidebar.warning("No entries found.")
        selected = None
    else:
        # Create entry options
        diary_options = []
        for entry in filtered_entries:
            option_str = f"{entry.get('date', 'Unknown')} - {extract_title_from_content(entry.get('content', ''))}"
            diary_options.append(option_str)
        
        selected = st.sidebar.radio("Select Entry:", options=diary_options)
        
        # Enhanced Entry Actions Menu
        if st.sidebar.button("βž• Entry Actions", key="entry_actions_btn"):
            st.session_state.show_entry_actions = not st.session_state.get('show_entry_actions', False)
            st.rerun()
        
        # Show entry actions menu if toggled
        if st.session_state.get('show_entry_actions', False):
            with st.sidebar.expander("🎯 Smart Actions", expanded=True):
                st.markdown("**Essential AI Functions:**")
                
                # Row 1
                col1, col2 = st.sidebar.columns(2)
                with col1:
                    if st.button("🎯 Extract Key Points", use_container_width=True, key="extract_btn"):
                        handle_entry_action("Summarize and extract the main key points from my diary entries. Focus on important decisions, lessons learned, significant events, and actionable insights.")
                    if st.button("⚑ Next Actions", use_container_width=True, key="next_actions_btn"):
                        handle_entry_action("Suggest concrete next actions and steps I should take based on my historical data, current goals, and diary patterns. What should I focus on this week?")
                    if st.button("🎯 Goal Tracker", use_container_width=True, key="goals_btn"):
                        handle_entry_action("Track my goals and objectives mentioned in diary entries. Analyze progress, identify stuck areas, and suggest ways to accelerate achievement.")
                
                with col2:
                    if st.button("οΏ½ Get Insights", use_container_width=True, key="insights_btn"):
                        handle_entry_action("Analyze my diary data and provide deep insights about my behavior patterns, productivity cycles, emotional states, and areas for improvement.")
                    if st.button("οΏ½ Strategy Plan", use_container_width=True, key="strategy_btn"):
                        handle_entry_action("Propose strategic plans and approaches based on the learned patterns from my diary. Help me create actionable strategies for achieving my goals.")
                    if st.button("⏰ Deadline Alert", use_container_width=True, key="deadline_btn"):
                        handle_entry_action("Review my diary for any mentioned deadlines, important dates, or time-sensitive tasks. Create alerts and reminders for upcoming important events.")
                
                # Close menu button
                if st.button("❌ Close Menu", use_container_width=True, key="close_entry_actions"):
                    st.session_state.show_entry_actions = False
                    st.rerun()
    
    # AI Status
    st.sidebar.markdown("---")
    st.sidebar.subheader("πŸ€– AI Status")
    
    # Check RAG service status
    service_running = check_rag_service()
    rag_status = st.session_state.get('rag_system_status', 'not_initialized')
    
    if not service_running:
        st.sidebar.error("❌ RAG Service Offline")
        st.sidebar.text("Start with: python start_rag_service.py")
    elif rag_status == "initialized":
        st.sidebar.success("βœ… AI Active")
        if rag_client:
            current_user_id = getattr(st.session_state, 'current_user_id', 1)
            status = rag_client.get_user_status(current_user_id)
            if status.get("document_count"):
                st.sidebar.metric("Documents", status.get("document_count", 0))
        
        # Fast mode toggle
        fast_mode = st.sidebar.checkbox(
            "Fast Mode", 
            value=st.session_state.get('fast_mode', False)
        )
        st.session_state.fast_mode = fast_mode
        
    elif rag_status == "ready_to_initialize":
        st.sidebar.info("πŸ”„ AI Ready")
        if st.sidebar.button("πŸš€ Initialize AI"):
            initialize_rag_system()
            st.rerun()
        
    else:
        st.sidebar.warning("⚠️ AI Unavailable")
        if service_running and st.sidebar.button("πŸ”„ Retry Initialize"):
            st.session_state.rag_system_status = "ready_to_initialize"
            st.rerun()
    
    # Detailed AI Diagnostics
    st.sidebar.markdown("---")
    current_user_id = getattr(st.session_state, 'current_user_id', 1)
    
    with st.sidebar.expander("πŸ” Detailed Diagnostics"):
        if service_running and rag_client:
            try:
                status = check_ai_availability_detailed(current_user_id)
                overall_status = status.get("overall_status", "unknown")
                
                if "checks" in status:
                    details = status["checks"]
                    
                    st.markdown("**Core Components:**")
                    # RAG Modules
                    rag_status = details.get("rag_modules", {})
                    if rag_status.get("available"):
                        st.markdown("βœ… RAG modules loaded")
                    else:
                        st.markdown("❌ RAG modules: Not available")
                    
                    # Google API Key
                    api_status = details.get("google_api_key", {})
                    if api_status.get("configured"):
                        st.markdown("βœ… Google API key configured")
                    else:
                        st.markdown("❌ Google API: Not configured")
                    
                    st.markdown("**User Data:**")
                    # Vector Database
                    vector_status = details.get("vector_database", {})
                    if vector_status.get("exists"):
                        st.markdown("βœ… Vector database ready")
                    else:
                        st.markdown("❌ Vector DB: Not found")
                    
                    # Document Count
                    doc_status = details.get("document_count", {})
                    count = doc_status.get("count", 0)
                    if count > 0:
                        st.markdown(f"βœ… {count} documents indexed")
                    else:
                        st.markdown("❌ No documents indexed")
                    
                    # Issues and fixes
                    issues = status.get("issues", [])
                    if issues:
                        st.markdown("**Issues Found:**")
                        for issue in issues:
                            st.markdown(f"⚠️ {issue}")
                    
                    fixes = status.get("suggested_fixes", [])
                    if fixes:
                        st.markdown("**Suggested Actions:**")
                        for fix in fixes:
                            st.markdown(f"πŸ”§ {fix}")
                        
                        # Auto-fix button
                        if st.button("πŸ”§ Attempt Auto-Fix", type="primary", key="sidebar_autofix"):
                            with st.spinner("Fixing AI availability issues..."):
                                fix_result = fix_ai_availability(current_user_id)
                                
                                if fix_result.get("status") == "success":
                                    st.success("βœ… AI availability issues fixed!")
                                    if fix_result.get("actions_taken"):
                                        st.info("Actions taken: " + ", ".join(fix_result["actions_taken"]))
                                    st.rerun()
                                else:
                                    st.error(f"❌ Fix failed: {fix_result.get('error', 'Unknown error')}")
                else:
                    st.warning("❌ Could not get detailed status")
            except Exception as e:
                st.error(f"❌ Diagnostics error: {str(e)}")
        else:
            st.warning("❌ RAG service not available")
    
    return selected

def display_selected_diary_entry(selected: str) -> None:
    """Display selected diary entry."""
    for entry in st.session_state.diary_entries:
        entry_identifier = f"{entry.get('date', 'Unknown')} - {extract_title_from_content(entry.get('content', ''))}"
        if entry_identifier == selected:
            # Header with delete button
            col1, col2 = st.columns([4, 1])
            
            with col1:
                st.header(f"πŸ“ {entry.get('date', 'Unknown')} - {extract_title_from_content(entry.get('content', ''))}")
            
            with col2:
                if st.button("πŸ—‘οΈ Delete", key=f"delete_{entry.get('id')}", type="secondary"):
                    st.session_state.show_delete_confirm = entry.get('id')
                    st.rerun()
            
            # Display tags
            entry_tags = entry.get('tags', '')
            if entry_tags:
                tag_list = [tag.strip() for tag in entry_tags.split(',') if tag.strip()]
                if tag_list:
                    st.markdown("**Tags:**")
                    st.markdown(render_tags(tag_list), unsafe_allow_html=True)
            
            # Display content
            st.markdown("---")
            st.write(extract_content_from_entry(entry.get('content', '')))
            
            # Handle deletion
            if (hasattr(st.session_state, 'show_delete_confirm') and 
                st.session_state.show_delete_confirm == entry.get('id')):
                
                st.markdown("---")
                st.warning("⚠️ **Confirm Deletion**")
                st.write(f"Delete: **{extract_title_from_content(entry.get('content', ''))}**?")
                
                col1, col2 = st.columns(2)
                
                with col1:
                    if st.button("βœ… Yes, Delete", type="primary"):
                        user_id = getattr(st.session_state, 'current_user_id', 1)
                        
                        with st.spinner("πŸ—‘οΈ Deleting entry and rebuilding search index..."):
                            # Step 1: Delete the diary entry from database
                            success = delete_diary_entry(entry.get('id'), user_id)
                            
                            if success:
                                # Step 2: Delete vector database to ensure clean rebuild
                                if rag_client and check_rag_service():
                                    try:
                                        st.info("πŸ”„ Clearing vector database...")
                                        delete_result = rag_client.delete_vector_db(user_id)
                                        
                                        if delete_result.get("status") == "success":
                                            st.info("βœ… Vector database cleared successfully")
                                        else:
                                            st.warning(f"⚠️ Vector DB deletion warning: {delete_result.get('error', 'Unknown')}")
                                    
                                    except Exception as e:
                                        st.warning(f"⚠️ Could not clear vector database: {str(e)}")
                                    
                                    # Step 3: Full re-indexing of all remaining documents
                                    st.info("πŸ”„ Rebuilding search index from all remaining entries...")
                                    try:
                                        index_result = rag_client.index_user_data(user_id, clear_existing=True)
                                        
                                        if index_result.get("status") == "success":
                                            docs_count = index_result.get('documents_processed', 0)
                                            st.success(f"βœ… Search index rebuilt with {docs_count} documents")
                                        else:
                                            st.warning(f"⚠️ Re-indexing failed: {index_result.get('error', 'Unknown error')}")
                                    
                                    except Exception as e:
                                        st.error(f"❌ Re-indexing error: {str(e)}")
                                else:
                                    st.warning("⚠️ RAG service not available - entry deleted but search index not updated")
                                
                                # Step 4: Refresh UI
                                st.session_state.diary_entries = load_entries_from_database(user_id)
                                del st.session_state.show_delete_confirm
                                st.success("βœ… Entry deleted and search index rebuilt!")
                                st.rerun()
                            else:
                                st.error("❌ Failed to delete diary entry")
                
                with col2:
                    if st.button("❌ Cancel"):
                        del st.session_state.show_delete_confirm
                        st.rerun()
            break

def render_diary_entry_form() -> None:
    """Render diary entry form."""
    st.header("✍️ Add New Diary Entry")
    st.markdown("---")
    
    date = st.date_input("πŸ“… Date", value=datetime.now().date())
    title = st.text_input("πŸ“Œ Title", placeholder="Enter title...")
    audio = st.audio_input("Record your audio")
    # Prevent infinite rerun loop by using a flag
    if audio and not st.session_state.get('voice_transcribed_content') and not st.session_state.get('audio_transcribed_once'):
        with open("./temp/recorded_audio.wav", "wb") as f:
            f.write(audio.getbuffer())
        st.success("Audio recorded and saved successfully!")
        user_id = getattr(st.session_state, 'current_user_id', 1)
        with st.spinner("πŸ”„ Transcribing audio..."):
            transcribed_text = transcribe_audio_with_gemini_live(audio.getbuffer(), user_id)
            if transcribed_text and not transcribed_text.startswith("❌") and not transcribed_text.startswith("⚠️"):
                st.session_state.voice_transcribed_content = transcribed_text
                st.session_state.audio_transcribed_once = True
                st.success("βœ… Voice transcribed successfully!")
                st.rerun()
            else:
                st.session_state.audio_transcribed_once = True
                st.error(transcribed_text or "Failed to transcribe audio")
    # Reset the flag if no audio is present
    if not audio and st.session_state.get('audio_transcribed_once'):
        st.session_state.audio_transcribed_once = False
    
    # Content textarea - use transcribed content if available
    content_value = st.session_state.get('voice_transcribed_content', '')
    if not content_value:
        content_value = st.session_state.get('current_content', '')
    
    content = st.text_area(
        "πŸ“– Content",
        value=content_value,
        placeholder="Write your diary entry... Use #tags! Or use voice input above.",
        height=150,
        key="diary_content_input"
    )
    
    # Clear transcribed content after user sees it
    if 'voice_transcribed_content' in st.session_state:
        del st.session_state.voice_transcribed_content
        # Also reset the transcribed_once flag so next audio triggers transcription
        st.session_state.audio_transcribed_once = False
    
    # Tags
    st.markdown("### 🏷️ Tags")
    tags_input = st.text_input(
        "Tags (comma-separated)",
        placeholder="work, travel, family"
    )
    
    # Combine manual and auto tags
    manual_tags = parse_tags_input(tags_input)
    auto_tags = extract_tags_from_content(content) if content else []
    all_tags = list(set(manual_tags + auto_tags))
    
    # Show preview of all tags
    if all_tags:
        st.markdown("**Tags Preview:**")
        st.markdown(render_tags(all_tags), unsafe_allow_html=True)
    
    # Action buttons
    col1, col2 = st.columns(2)
    
    with col1:
        if st.button("πŸ’Ύ Save Entry", type="primary"):
            if title and content:
                user_id = getattr(st.session_state, 'current_user_id', 1)
                
                # Format content with title
                formatted_content = f"Title: {title}\nContent: {content}"
                tags_str = ','.join(all_tags) if all_tags else ''
                
                try:
                    # TαΊ‘o entry dictionary theo format mΓ  function cαΊ§n
                    entry = {
                        "date": date.strftime('%Y-%m-%d'),
                        "content": formatted_content,
                        "tags": tags_str
                    }
                    
                    # Call function vα»›i Δ‘ΓΊng format
                    success = submit_text_to_database(entry=entry, user_id=user_id)
                    
                    if success:
                        # Auto-sync after adding
                        run_auto_sync(user_id)
                        
                        # Refresh entries
                        st.session_state.diary_entries = load_entries_from_database(user_id)
                        st.session_state.show_form = False
                        
                        # Clear any remaining voice content
                        if 'voice_transcribed_content' in st.session_state:
                            del st.session_state.voice_transcribed_content
                        
                        st.success("βœ… Diary entry saved successfully!")
                        st.rerun()
                    else:
                        st.error("❌ Failed to save diary entry.")
                except Exception as e:
                    st.error(f"❌ Error saving entry: {str(e)}")
            else:
                st.warning("⚠️ Please fill in both title and content.")
    
    with col2:
        if st.button("❌ Cancel"):
            st.session_state.show_form = False
            # Clear any voice content
            if 'voice_transcribed_content' in st.session_state:
                del st.session_state.voice_transcribed_content
            st.rerun()

# ========================================
# MAIN APPLICATION
# ========================================
def main() -> None:
    """Main application function."""
    # Initialize authentication
    auth_ui = AuthUI()
    
    # Check if user is authenticated
    if not auth_ui.check_authentication():
        auth_ui.render_auth_page()
        return
    
    # Get current user info
    try:
        current_user_id = auth_ui.get_current_user_id()
        current_username = auth_ui.get_current_username()
        
        if current_user_id is None:
            current_user_id = 1
        if current_username is None:
            current_username = "User"
            
    except Exception as e:
        st.error(f"❌ Error getting user info: {str(e)}")
        current_user_id = 1
        current_username = "User"
    
    # Check if user changed - reset RAG system for data isolation
    if hasattr(st.session_state, 'current_user_id') and st.session_state.current_user_id != current_user_id:
        st.session_state.rag_system = None
        st.session_state.rag_system_status = "ready_to_initialize" if os.getenv("GOOGLE_API_KEY") else "no_api_key"
        st.session_state.messages = []
        st.session_state.diary_entries = []
        st.warning(f"πŸ”„ Switched to user {current_username}. RAG system reset for data isolation.")
    
    st.session_state.current_user_id = current_user_id
    st.session_state.current_username = current_username
    
    # App title
    st.title("πŸ€– Diary Chat Bot")
    st.markdown(f"*Welcome back, **{current_username}**! Your AI companion for managing diary entries*")
    
    # AI Status Widget
    if check_rag_service():
        render_ai_status_widget(current_user_id)
    else:
        st.error("❌ **RAG Service is offline**")
        st.info("πŸ’‘ Start the service with: `python start_rag_service.py`")
    
    st.markdown("---")
    
    # Initialize session state
    initialize_session_state()
    
    # Force reload diary entries for current user
    if not st.session_state.diary_entries:
        st.session_state.diary_entries = load_entries_from_database(current_user_id)
    
    # Initialize RAG system if ready
    if st.session_state.get('rag_system_status') == 'ready_to_initialize':
        initialize_rag_system()
    
    # Render sidebar and get selected entry
    auth_ui.render_user_profile()
    selected_entry = render_sidebar()
    
    # Display selected diary entry
    st.markdown("---")
    if st.session_state.diary_entries and selected_entry:
        display_selected_diary_entry(selected_entry)
    elif not st.session_state.diary_entries:
        st.info("πŸ“ No diary entries found. Click 'βž• Add New Entry' in the sidebar to get started!")
    else:
        st.info("πŸ“– Select a diary entry from the sidebar to view its content.")
    
    # Chat section
    st.markdown("---")
    st.subheader("πŸ’¬ Chat with your AI Assistant")
    
    display_chat_history()
    handle_chat_input()
    
    # Diary entry form
    if st.session_state.show_form:
        st.markdown("---")
        render_diary_entry_form()

if __name__ == "__main__":
    main()